Explorar o código

Add support for defining policies through oslo.policy.

Nashwan Azhari %!s(int64=7) %!d(string=hai) anos
pai
achega
571a43f4d0

+ 6 - 2
coriolis/api/v1/endpoint_actions.py

@@ -3,9 +3,10 @@
 
 from webob import exc
 
+from coriolis import exception
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoints import api
-from coriolis import exception
+from coriolis.policies import endpoints as endpoint_policies
 
 
 class EndpointActionsController(api_wsgi.Controller):
@@ -15,9 +16,12 @@ class EndpointActionsController(api_wsgi.Controller):
 
     @api_wsgi.action('validate-connection')
     def _validate_connection(self, req, id, body):
+        context = req.environ['coriolis.context']
+        context.can("%s:validate_connection" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
         try:
             is_valid, message = self._endpoint_api.validate_connection(
-                req.environ['coriolis.context'], id)
+                context, id)
             return {
                 "validate-connection":
                     {"valid": is_valid, "message": message}

+ 8 - 3
coriolis/api/v1/endpoint_destination_options.py

@@ -3,10 +3,12 @@
 
 from oslo_log import log as logging
 
+from coriolis import utils
 from coriolis.api.v1.views import endpoint_destination_options_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoint_destination_options import api
-from coriolis import utils
+from coriolis.policies import endpoints as endpoint_policies
+
 
 LOG = logging.getLogger(__name__)
 
@@ -17,6 +19,10 @@ class EndpointDestinationOptionsController(api_wsgi.Controller):
         super(EndpointDestinationOptionsController, self).__init__()
 
     def index(self, req, endpoint_id):
+        context = req.environ['coriolis.context']
+        context.can("%s:list_destination_options" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
+
         env = req.GET.get("env")
         if env is not None:
             env = utils.decode_base64_param(env, is_json=True)
@@ -28,8 +34,7 @@ class EndpointDestinationOptionsController(api_wsgi.Controller):
         return endpoint_destination_options_view.collection(
             req,
             self._destination_options_api.get_endpoint_destination_options(
-                req.environ['coriolis.context'], endpoint_id,
-                env=env, option_names=options))
+                context, endpoint_id, env=env, option_names=options))
 
 
 def create_resource():

+ 10 - 3
coriolis/api/v1/endpoint_instances.py

@@ -3,11 +3,12 @@
 
 from oslo_log import log as logging
 
+from coriolis import utils
 from coriolis.api import common
 from coriolis.api.v1.views import endpoint_instance_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoint_instances import api
-from coriolis import utils
+from coriolis.policies import endpoints as endpoint_policies
 
 LOG = logging.getLogger(__name__)
 
@@ -18,15 +19,21 @@ class EndpointInstanceController(api_wsgi.Controller):
         super(EndpointInstanceController, self).__init__()
 
     def index(self, req, endpoint_id):
+        context = req.environ['coriolis.context']
+        context.can("%s:list_instances" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
         marker, limit = common.get_paging_params(req)
         instance_name_pattern = req.GET.get("name")
 
         return endpoint_instance_view.collection(
             req, self._instance_api.get_endpoint_instances(
-                req.environ['coriolis.context'], endpoint_id, marker, limit,
-                instance_name_pattern))
+                context, endpoint_id, marker, limit, instance_name_pattern))
 
     def show(self, req, endpoint_id, id):
+        context = req.environ['coriolis.context']
+        context.can("%s:get_instance" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
+
         # WSGI does not allow encoded / chars (%2F) in the url
         # See e.g.: https://github.com/pallets/flask/issues/900
         id = utils.decode_base64_param(id)

+ 6 - 2
coriolis/api/v1/endpoint_networks.py

@@ -3,10 +3,11 @@
 
 from oslo_log import log as logging
 
+from coriolis import utils
 from coriolis.api.v1.views import endpoint_network_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoint_networks import api
-from coriolis import utils
+from coriolis.policies import endpoints as endpoint_policies
 
 LOG = logging.getLogger(__name__)
 
@@ -17,13 +18,16 @@ class EndpointNetworkController(api_wsgi.Controller):
         super(EndpointNetworkController, self).__init__()
 
     def index(self, req, endpoint_id):
+        context = req.environ['coriolis.context']
+        context.can("%s:list_networks" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
         env = req.GET.get("env")
         if env is not None:
             env = utils.decode_base64_param(env, is_json=True)
 
         return endpoint_network_view.collection(
             req, self._network_api.get_endpoint_networks(
-                req.environ['coriolis.context'], endpoint_id, env))
+                context, endpoint_id, env))
 
 
 def create_resource():

+ 15 - 7
coriolis/api/v1/endpoints.py

@@ -4,10 +4,11 @@
 from oslo_log import log as logging
 from webob import exc
 
+from coriolis import exception
 from coriolis.api.v1.views import endpoint_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoints import api
-from coriolis import exception
+from coriolis.policies import endpoints as endpoint_policies
 
 LOG = logging.getLogger(__name__)
 
@@ -18,17 +19,19 @@ class EndpointController(api_wsgi.Controller):
         super(EndpointController, self).__init__()
 
     def show(self, req, id):
-        endpoint = self._endpoint_api.get_endpoint(
-            req.environ["coriolis.context"], id)
+        context = req.environ["coriolis.context"]
+        context.can(endpoint_policies.get_endpoints_policy_label("show"))
+        endpoint = self._endpoint_api.get_endpoint(context, id)
         if not endpoint:
             raise exc.HTTPNotFound()
 
         return endpoint_view.single(req, endpoint)
 
     def index(self, req):
+        context = req.environ["coriolis.context"]
+        context.can(endpoint_policies.get_endpoints_policy_label("list"))
         return endpoint_view.collection(
-            req, self._endpoint_api.get_endpoints(
-                req.environ['coriolis.context']))
+            req, self._endpoint_api.get_endpoints(context))
 
     def _validate_create_body(self, body):
         try:
@@ -47,11 +50,12 @@ class EndpointController(api_wsgi.Controller):
             raise exception.InvalidInput(msg)
 
     def create(self, req, body):
+        context = req.environ["coriolis.context"]
+        context.can(endpoint_policies.get_endpoints_policy_label("create"))
         (name, endpoint_type, description,
          connection_info) = self._validate_create_body(body)
         return endpoint_view.single(req, self._endpoint_api.create(
-            req.environ['coriolis.context'], name, endpoint_type, description,
-            connection_info))
+            context, name, endpoint_type, description, connection_info))
 
     def _validate_update_body(self, body):
         try:
@@ -67,11 +71,15 @@ class EndpointController(api_wsgi.Controller):
             raise exception.InvalidInput(msg)
 
     def update(self, req, id, body):
+        context = req.environ["coriolis.context"]
+        context.can(endpoint_policies.get_endpoints_policy_label("update"))
         updated_values = self._validate_update_body(body)
         return endpoint_view.single(req, self._endpoint_api.update(
             req.environ['coriolis.context'], id, updated_values))
 
     def delete(self, req, id):
+        context = req.environ["coriolis.context"]
+        context.can(endpoint_policies.get_endpoints_policy_label("delete"))
         try:
             self._endpoint_api.delete(req.environ['coriolis.context'], id)
             raise exc.HTTPNoContent()

+ 5 - 3
coriolis/api/v1/migration_actions.py

@@ -3,9 +3,10 @@
 
 from webob import exc
 
-from coriolis.api import wsgi as api_wsgi
 from coriolis import exception
+from coriolis.api import wsgi as api_wsgi
 from coriolis.migrations import api
+from coriolis.policies import migrations as migration_policies
 
 
 class MigrationActionsController(api_wsgi.Controller):
@@ -15,11 +16,12 @@ class MigrationActionsController(api_wsgi.Controller):
 
     @api_wsgi.action('cancel')
     def _cancel(self, req, id, body):
+        context = req.environ['coriolis.context']
+        context.can(migration_policies.get_migrations_policy_label("cancel"))
         try:
             force = (body["cancel"] or {}).get("force", False)
 
-            self._migration_api.cancel(
-                req.environ['coriolis.context'], id, force)
+            self._migration_api.cancel(context, id, force)
             raise exc.HTTPNoContent()
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)

+ 16 - 6
coriolis/api/v1/migrations.py

@@ -4,11 +4,12 @@
 from oslo_log import log as logging
 from webob import exc
 
+from coriolis import exception
 from coriolis.api.v1.views import migration_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoints import api as endpoints_api
-from coriolis import exception
 from coriolis.migrations import api
+from coriolis.policies import migrations as migration_policies
 
 LOG = logging.getLogger(__name__)
 
@@ -20,22 +21,28 @@ class MigrationController(api_wsgi.Controller):
         super(MigrationController, self).__init__()
 
     def show(self, req, id):
-        migration = self._migration_api.get_migration(
-            req.environ["coriolis.context"], id)
+        context = req.environ["coriolis.context"]
+        context.can(migration_policies.get_migrations_policy_label("show"))
+        migration = self._migration_api.get_migration(context, id)
         if not migration:
             raise exc.HTTPNotFound()
 
         return migration_view.single(req, migration)
 
     def index(self, req):
+        context = req.environ["coriolis.context"]
+        context.can(migration_policies.get_migrations_policy_label("show"))
         return migration_view.collection(
             req, self._migration_api.get_migrations(
-                req.environ['coriolis.context'], include_tasks=False))
+                context, include_tasks=False))
 
     def detail(self, req):
+        context = req.environ["coriolis.context"]
+        context.can(
+            migration_policies.get_migrations_policy_label("show_execution"))
         return migration_view.collection(
             req, self._migration_api.get_migrations(
-                req.environ['coriolis.context'], include_tasks=True))
+                context, include_tasks=True))
 
     def _validate_migration_input(self, migration):
         try:
@@ -61,6 +68,7 @@ class MigrationController(api_wsgi.Controller):
         # TODO(alexpilotti): validate body
         migration_body = body.get("migration", {})
         context = req.environ['coriolis.context']
+        context.can(migration_policies.get_migrations_policy_label("create"))
 
         replica_id = migration_body.get("replica_id")
         if replica_id:
@@ -95,8 +103,10 @@ class MigrationController(api_wsgi.Controller):
         return migration_view.single(req, migration)
 
     def delete(self, req, id):
+        context = req.environ['coriolis.context']
+        context.can(migration_policies.get_migrations_policy_label("delete"))
         try:
-            self._migration_api.delete(req.environ['coriolis.context'], id)
+            self._migration_api.delete(context, id)
             raise exc.HTTPNoContent()
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)

+ 5 - 2
coriolis/api/v1/providers.py

@@ -5,6 +5,7 @@ from oslo_log import log as logging
 
 from coriolis.api import wsgi as api_wsgi
 from coriolis.providers import api
+from coriolis.policies import general as general_policies
 
 LOG = logging.getLogger(__name__)
 
@@ -15,8 +16,10 @@ class ProviderController(api_wsgi.Controller):
         super(ProviderController, self).__init__()
 
     def index(self, req):
-        return {"providers": self._provider_api.get_available_providers(
-            req.environ['coriolis.context'])}
+        context = req.environ['coriolis.context']
+        context.can(general_policies.get_providers_policy_label('list'))
+        return {
+            "providers": self._provider_api.get_available_providers(context)}
 
 
 def create_resource():

+ 5 - 3
coriolis/api/v1/replica_actions.py

@@ -3,9 +3,10 @@
 
 from webob import exc
 
+from coriolis import exception
 from coriolis.api.v1.views import replica_tasks_execution_view
 from coriolis.api import wsgi as api_wsgi
-from coriolis import exception
+from coriolis.policies import replicas as replica_policies
 from coriolis.replicas import api
 
 
@@ -16,10 +17,11 @@ class ReplicaActionsController(api_wsgi.Controller):
 
     @api_wsgi.action('delete-disks')
     def _delete_disks(self, req, id, body):
+        context = req.environ['coriolis.context']
+        context.can(replica_policies.get_replicas_policy_label("delete_disks"))
         try:
             return replica_tasks_execution_view.single(
-                req, self._replica_api.delete_disks(
-                    req.environ['coriolis.context'], id))
+                req, self._replica_api.delete_disks(context, id))
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:

+ 32 - 17
coriolis/api/v1/replica_schedules.py

@@ -1,18 +1,19 @@
 # Copyright 2017 Cloudbase Solutions Srl
 # All Rights Reserved.
+
+import jsonschema
+from oslo_log import log as logging
+from oslo_utils import strutils
+from oslo_utils import timeutils
 from webob import exc
 
+from coriolis import exception
+from coriolis import schemas
 from coriolis.api.v1.views import replica_schedule_view
 from coriolis.api import wsgi as api_wsgi
-from coriolis import exception
+from coriolis.policies import replica_schedules as schedules_policies
 from coriolis.replica_cron import api
-from coriolis import schemas
 
-import jsonschema
-
-from oslo_log import log as logging
-from oslo_utils import strutils
-from oslo_utils import timeutils
 
 LOG = logging.getLogger(__name__)
 
@@ -23,20 +24,25 @@ class ReplicaScheduleController(api_wsgi.Controller):
         super(ReplicaScheduleController, self).__init__()
 
     def show(self, req, replica_id, id):
-        schedule = self._schedule_api.get_schedule(
-            req.environ["coriolis.context"], replica_id, id)
+        context = req.environ["coriolis.context"]
+        context.can(
+            schedules_policies.get_replica_schedules_policy_label("show"))
+        schedule = self._schedule_api.get_schedule(context, replica_id, id)
         if not schedule:
             raise exc.HTTPNotFound()
 
         return replica_schedule_view.single(req, schedule)
 
     def index(self, req, replica_id):
+        context = req.environ["coriolis.context"]
+        context.can(
+            schedules_policies.get_replica_schedules_policy_label("list"))
+
         show_expired = strutils.bool_from_string(
             req.GET.get("show_expired", True), strict=True)
         return replica_schedule_view.collection(
             req, self._schedule_api.get_schedules(
-                req.environ['coriolis.context'], replica_id,
-                expired=show_expired))
+                context, replica_id, expired=show_expired))
 
     def _validate_schedule(self, schedule):
         schema = schemas.SCHEDULE_API_BODY_SCHEMA["properties"]["schedule"]
@@ -95,6 +101,10 @@ class ReplicaScheduleController(api_wsgi.Controller):
         return body
 
     def create(self, req, replica_id, body):
+        context = req.environ["coriolis.context"]
+        context.can(
+            schedules_policies.get_replica_schedules_policy_label("create"))
+
         LOG.debug("Got request: %r %r %r" % (req, replica_id, body))
         try:
             schedule, enabled, exp_date, shutdown = self._validate_create_body(
@@ -103,10 +113,13 @@ class ReplicaScheduleController(api_wsgi.Controller):
             raise exception.InvalidInput(err)
 
         return replica_schedule_view.single(req, self._schedule_api.create(
-            req.environ['coriolis.context'], replica_id, schedule, enabled,
-            exp_date, shutdown))
+            context, replica_id, schedule, enabled, exp_date, shutdown))
 
     def update(self, req, replica_id, id, body):
+        context = req.environ["coriolis.context"]
+        context.can(
+            schedules_policies.get_replica_schedules_policy_label("update"))
+
         LOG.debug("Got request: %r %r %r %r" % (
             req, replica_id, id, body))
 
@@ -116,12 +129,14 @@ class ReplicaScheduleController(api_wsgi.Controller):
             raise exception.InvalidInput(err)
 
         return replica_schedule_view.single(req, self._schedule_api.update(
-            req.environ['coriolis.context'], replica_id, id,
-            update_values))
+            context, replica_id, id, update_values))
 
     def delete(self, req, replica_id, id):
-        self._schedule_api.delete(
-            req.environ['coriolis.context'], replica_id, id)
+        context = req.environ["coriolis.context"]
+        context.can(
+            schedules_policies.get_replica_schedules_policy_label("delete"))
+
+        self._schedule_api.delete(context, replica_id, id)
         raise exc.HTTPNoContent()
 
 

+ 6 - 2
coriolis/api/v1/replica_tasks_execution_actions.py

@@ -3,8 +3,9 @@
 
 from webob import exc
 
-from coriolis.api import wsgi as api_wsgi
 from coriolis import exception
+from coriolis.api import wsgi as api_wsgi
+from coriolis.policies import replica_tasks_executions as execution_policies
 from coriolis.replica_tasks_executions import api
 
 
@@ -15,11 +16,14 @@ class ReplicaTasksExecutionActionsController(api_wsgi.Controller):
 
     @api_wsgi.action('cancel')
     def _cancel(self, req, replica_id, id, body):
+        context = req.environ['coriolis.context']
+        context.can(
+            execution_policies.get_replica_executions_policy_label('cancel'))
         try:
             force = (body["cancel"] or {}).get("force", False)
 
             self._replica_tasks_execution_api.cancel(
-                req.environ['coriolis.context'], replica_id, id, force)
+                context, replica_id, id, force)
             raise exc.HTTPNoContent()
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)

+ 20 - 7
coriolis/api/v1/replica_tasks_executions.py

@@ -7,6 +7,7 @@ from coriolis.api.v1.views import replica_tasks_execution_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis import exception
 from coriolis.replica_tasks_executions import api
+from coriolis.policies import replica_tasks_executions as executions_policies
 
 
 class ReplicaTasksExecutionController(api_wsgi.Controller):
@@ -15,18 +16,24 @@ class ReplicaTasksExecutionController(api_wsgi.Controller):
         super(ReplicaTasksExecutionController, self).__init__()
 
     def show(self, req, replica_id, id):
+        context = req.environ["coriolis.context"]
+        context.can(
+            executions_policies.get_replica_executions_policy_label("show"))
         execution = self._replica_tasks_execution_api.get_execution(
-            req.environ["coriolis.context"], replica_id, id)
+            context, replica_id, id)
         if not execution:
             raise exc.HTTPNotFound()
 
         return replica_tasks_execution_view.single(req, execution)
 
     def index(self, req, replica_id):
+        context = req.environ["coriolis.context"]
+        context.can(
+            executions_policies.get_replica_executions_policy_label("list"))
+
         return replica_tasks_execution_view.collection(
             req, self._replica_tasks_execution_api.get_executions(
-                req.environ['coriolis.context'], replica_id,
-                include_tasks=False))
+                context, replica_id, include_tasks=False))
 
     def detail(self, req, replica_id):
         return replica_tasks_execution_view.collection(
@@ -35,6 +42,10 @@ class ReplicaTasksExecutionController(api_wsgi.Controller):
                 include_tasks=True))
 
     def create(self, req, replica_id, body):
+        context = req.environ["coriolis.context"]
+        context.can(
+            executions_policies.get_replica_executions_policy_label("create"))
+
         # TODO(alexpilotti): validate body
 
         execution_body = body.get("execution", {})
@@ -42,13 +53,15 @@ class ReplicaTasksExecutionController(api_wsgi.Controller):
 
         return replica_tasks_execution_view.single(
             req, self._replica_tasks_execution_api.create(
-                req.environ['coriolis.context'], replica_id,
-                shutdown_instances))
+                context, replica_id, shutdown_instances))
 
     def delete(self, req, replica_id, id):
+        context = req.environ["coriolis.context"]
+        context.can(
+            executions_policies.get_replica_executions_policy_label("delete"))
+
         try:
-            self._replica_tasks_execution_api.delete(
-                req.environ['coriolis.context'], replica_id, id)
+            self._replica_tasks_execution_api.delete(context, replica_id, id)
             raise exc.HTTPNoContent()
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)

+ 21 - 12
coriolis/api/v1/replicas.py

@@ -4,10 +4,11 @@
 from oslo_log import log as logging
 from webob import exc
 
+from coriolis import exception
 from coriolis.api.v1.views import replica_view
 from coriolis.api import wsgi as api_wsgi
 from coriolis.endpoints import api as endpoints_api
-from coriolis import exception
+from coriolis.policies import replicas as replica_policies
 from coriolis.replicas import api
 
 LOG = logging.getLogger(__name__)
@@ -20,24 +21,28 @@ class ReplicaController(api_wsgi.Controller):
         super(ReplicaController, self).__init__()
 
     def show(self, req, id):
-        replica = self._replica_api.get_replica(
-            req.environ["coriolis.context"], id)
+        context = req.environ["coriolis.context"]
+        context.can(replica_policies.get_replicas_policy_label("show"))
+        replica = self._replica_api.get_replica(context, id)
         if not replica:
             raise exc.HTTPNotFound()
 
         return replica_view.single(req, replica)
 
     def index(self, req):
+        context = req.environ["coriolis.context"]
+        context.can(replica_policies.get_replicas_policy_label("list"))
         return replica_view.collection(
             req, self._replica_api.get_replicas(
-                req.environ['coriolis.context'],
-                include_tasks_executions=False))
+                context, include_tasks_executions=False))
 
     def detail(self, req):
+        context = req.environ["coriolis.context"]
+        context.can(
+            replica_policies.get_replicas_policy_label("show_executions"))
         return replica_view.collection(
             req, self._replica_api.get_replicas(
-                req.environ['coriolis.context'],
-                include_tasks_executions=True))
+                context, include_tasks_executions=True))
 
     def _validate_create_body(self, body):
         try:
@@ -60,13 +65,16 @@ class ReplicaController(api_wsgi.Controller):
             raise exception.InvalidInput(msg)
 
     def create(self, req, body):
+        context = req.environ["coriolis.context"]
+        context.can(replica_policies.get_replicas_policy_label("create"))
+
         (origin_endpoint_id, destination_endpoint_id,
          destination_environment, instances,
          notes) = self._validate_create_body(body)
 
         is_valid, message = (
             self._endpoints_api.validate_target_environment(
-                req.environ["coriolis.context"], destination_endpoint_id,
+                context, destination_endpoint_id,
                 destination_environment))
         if not is_valid:
             raise exc.HTTPBadRequest(
@@ -74,13 +82,14 @@ class ReplicaController(api_wsgi.Controller):
                             "environment: %s" % message)
 
         return replica_view.single(req, self._replica_api.create(
-            req.environ['coriolis.context'], origin_endpoint_id,
-            destination_endpoint_id, destination_environment, instances,
-            notes))
+            context, origin_endpoint_id, destination_endpoint_id,
+            destination_environment, instances, notes))
 
     def delete(self, req, id):
+        context = req.environ["coriolis.context"]
+        context.can(replica_policies.get_replicas_policy_label("delete"))
         try:
-            self._replica_api.delete(req.environ['coriolis.context'], id)
+            self._replica_api.delete(context, id)
             raise exc.HTTPNoContent()
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)

+ 32 - 0
coriolis/context.py

@@ -1,10 +1,15 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+import copy
+
 from oslo_context import context
 from oslo_db.sqlalchemy import enginefacade
 from oslo_utils import timeutils
 
+from coriolis import exception
+from coriolis import policy
+
 
 @enginefacade.transaction_context_provider
 class RequestContext(context.RequestContext):
@@ -66,6 +71,33 @@ class RequestContext(context.RequestContext):
     def from_dict(cls, values):
         return cls(**values)
 
+    def to_policy_values(self):
+        policy = super(RequestContext, self).to_policy_values()
+        # TODO(aznashwan): determine if there are any other custom
+        # context params we'd like to be used for policy validation:
+        return policy
+
+    def can(self, action, target=None, fatal=True):
+        """ Validates policies allow the requested action to be
+        perfomed in the given context, and raises otherwise.
+        """
+        default_target = {
+            'project_id': self.project_id, 'user_id': self.user_id}
+        if target is None:
+            target = default_target
+        else:
+            target = copy.deepcopy(target)
+            target.update(default_target)
+
+        result = False
+        try:
+            result = policy.check_policy_for_context(self, action, target)
+        except exception.PolicyNotAuthorized:
+            if fatal:
+                raise
+
+        return result
+
 
 def get_admin_context(trust_id=None):
     return RequestContext(

+ 6 - 0
coriolis/exception.py

@@ -123,6 +123,12 @@ class NotAuthorized(CoriolisException):
     safe = True
 
 
+class PolicyNotAuthorized(CoriolisException):
+    message = _("Not authorized via policy.")
+    code = 403
+    safe = True
+
+
 class Conflict(CoriolisException):
     message = _("Conflict")
     code = 409

+ 0 - 0
coriolis/policies/__init__.py


+ 25 - 0
coriolis/policies/base.py

@@ -0,0 +1,25 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+
+# NOTE: the policy prefix is convened to be the 'service_type' of
+# the Coriolis endpoint in the Keystone Service Catalog.
+CORIOLIS_POLICIES_PREFIX = "migration"
+
+# NOTE: the below are default rules which can be fallen back to:
+POLICY_DEFAULT_RULES = [
+    policy.RuleDefault(
+        "admin",
+        "is_admin:True",
+        "Default admin rule which is omnipotent."),
+    policy.RuleDefault(
+        "admin_or_owner",
+        "is_admin:True or project_id:%(project_id)s",
+        "Default rule for most API accesses."),
+]
+
+
+def list_rules():
+    return POLICY_DEFAULT_RULES

+ 135 - 0
coriolis/policies/endpoints.py

@@ -0,0 +1,135 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+ENDPOINTS_POLICY_PREFIX = "%s:endpoints" % base.CORIOLIS_POLICIES_PREFIX
+ENDPOINTS_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_endpoints_policy_label(rule_label):
+    return "%s:%s" % (
+        ENDPOINTS_POLICY_PREFIX, rule_label)
+
+
+ENDPOINTS_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('create'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Create an endpoint",
+        [
+            {
+                "path": "/endpoints",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List endpoints",
+        [
+            {
+                "path": "/endpoints",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('show'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Show details for endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    # TODO(aznashwan): double-check this:
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('update'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Update details for endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}",
+                "method": "PUT"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('delete'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Delete endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}",
+                "method": "DELETE"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('validate_connection'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Validate endpoint connection info",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/action",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list_instances'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List instances available for migration/replication",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/instances",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('get_instance'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "Get details for given instance available for migration/replication",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/instances/{instance_name}",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list_networks'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List networks available on the given endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/networks",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list_destination_options'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List available destination options for endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/destination-options",
+                "method": "GET"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return ENDPOINTS_POLICY_DEFAULT_RULES

+ 34 - 0
coriolis/policies/general.py

@@ -0,0 +1,34 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+PROVIDERS_POLICY_PREFIX = "%s:providers" % base.CORIOLIS_POLICIES_PREFIX
+PROVIDERS_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_providers_policy_label(rule_label):
+    return "%s:%s" % (
+        PROVIDERS_POLICY_DEFAULT_RULE, rule_label)
+
+
+PROVIDERS_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_providers_policy_label('list'),
+        PROVIDERS_POLICY_DEFAULT_RULE,
+        "List available provider plugins and their capabilities",
+        [
+            {
+                "path": "/providers",
+                "method": "GET"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return PROVIDERS_POLICY_DEFAULT_RULES

+ 91 - 0
coriolis/policies/migrations.py

@@ -0,0 +1,91 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+MIGRATIONS_POLICY_PREFIX = "%s:migrations" % base.CORIOLIS_POLICIES_PREFIX
+MIGRATIONS_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_migrations_policy_label(rule_label):
+    return "%s:%s" % (
+        MIGRATIONS_POLICY_PREFIX, rule_label)
+
+
+MIGRATIONS_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('create'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "Create a migration",
+        [
+            {
+                "path": "/migrations",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('list'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "List migrations",
+        [
+            {
+                "path": "/migrations",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('show'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "Show details for a migration",
+        [
+            {
+                "path": "/migration/{migration_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('show_execution'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "Show details for a migration (including tasks execution)",
+        [
+            {
+                "path": "/migration/{migration_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    # TODO(aznashwan): migration actions should ideally be
+    # declared in a separate module
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('cancel'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "Cancel a running Migration",
+        [
+            {
+                "path": "/migrations/{migration_id}/actions/",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_migrations_policy_label('delete'),
+        MIGRATIONS_POLICY_DEFAULT_RULE,
+        "Delete Migration",
+        [
+            {
+                "path": "/migration/{migration_id}",
+                "method": "DELETE"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return MIGRATIONS_POLICY_DEFAULT_RULES

+ 80 - 0
coriolis/policies/replica_schedules.py

@@ -0,0 +1,80 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+REPLICA_SCHEDULES_POLICY_PREFIX = "%s:replica_schedules" % (
+    base.CORIOLIS_POLICIES_PREFIX)
+REPLICA_SCHEDULES_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_replica_schedules_policy_label(rule_label):
+    return "%s:%s" % (
+        REPLICA_SCHEDULES_POLICY_PREFIX, rule_label)
+
+
+REPLICA_SCHEDULES_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_replica_schedules_policy_label('create'),
+        REPLICA_SCHEDULES_POLICY_DEFAULT_RULE,
+        "Create a new execution schedule for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/schedules",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_schedules_policy_label('list'),
+        REPLICA_SCHEDULES_POLICY_DEFAULT_RULE,
+        "List execution schedules for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/schedules",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_schedules_policy_label('show'),
+        REPLICA_SCHEDULES_POLICY_DEFAULT_RULE,
+        "Show details for an execution schedule for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/schedules/{schedule_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_schedules_policy_label('update'),
+        REPLICA_SCHEDULES_POLICY_DEFAULT_RULE,
+        "Update an existing execution schedule for a given Replica",
+        [
+            {
+                "path": (
+                    "/replicas/{replica_id}/schedules/{schedule_id}"),
+                "method": "PUT"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_schedules_policy_label('delete'),
+        REPLICA_SCHEDULES_POLICY_DEFAULT_RULE,
+        "Delete an execution schedule for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/schedules/{schedule_id}",
+                "method": "DELETE"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return REPLICA_SCHEDULES_POLICY_DEFAULT_RULES

+ 83 - 0
coriolis/policies/replica_tasks_executions.py

@@ -0,0 +1,83 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+REPLICA_EXECUTIONS_POLICY_PREFIX = "%s:replica_executions" % (
+    base.CORIOLIS_POLICIES_PREFIX)
+REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_replica_executions_policy_label(rule_label):
+    return "%s:%s" % (
+        REPLICA_EXECUTIONS_POLICY_PREFIX, rule_label)
+
+
+REPLICA_EXECUTIONS_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_replica_executions_policy_label('create'),
+        REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE,
+        "Create a new execution for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/executions",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_executions_policy_label('list'),
+        REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE,
+        "List Executions for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/executions",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_executions_policy_label('show'),
+        REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE,
+        "Show details for Replica execution",
+        [
+            {
+                "path": "/replicas/{replica_id}/executions/{execution_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    # TODO(aznashwan): replica actions should ideally be
+    # declared in a separate module
+    policy.DocumentedRuleDefault(
+        get_replica_executions_policy_label('cancel'),
+        REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE,
+        "Cancel a Replica execution",
+        [
+            {
+                "path": (
+                    "/replicas/{replica_id}/executions/"
+                    "{execution_id}/actions"),
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replica_executions_policy_label('delete'),
+        REPLICA_EXECUTIONS_POLICY_DEFAULT_RULE,
+        "Delete an execution for a given Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}/executions/{execution_id}",
+                "method": "DELETE"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return REPLICA_EXECUTIONS_POLICY_DEFAULT_RULES

+ 91 - 0
coriolis/policies/replicas.py

@@ -0,0 +1,91 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_policy import policy
+
+from coriolis.policies import base
+
+
+REPLICAS_POLICY_PREFIX = "%s:replicas" % base.CORIOLIS_POLICIES_PREFIX
+REPLICAS_POLICY_DEFAULT_RULE = "rule:admin_or_owner"
+
+
+def get_replicas_policy_label(rule_label):
+    return "%s:%s" % (
+        REPLICAS_POLICY_PREFIX, rule_label)
+
+
+REPLICAS_POLICY_DEFAULT_RULES = [
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('create'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "Create a Replica",
+        [
+            {
+                "path": "/replicas",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('list'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "List Replicas",
+        [
+            {
+                "path": "/replicas",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('show'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "Show details for Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('show_executions'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "Show details for Replica (including tasks executions)",
+        [
+            {
+                "path": "/replicas/{replica_id}",
+                "method": "GET"
+            }
+        ]
+    ),
+    # TODO(aznashwan): replica actions should ideally be
+    # declared in a separate module
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('delete_disks'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "Delete Replica Disks",
+        [
+            {
+                "path": "/replicas/{replica_id}/actions",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_replicas_policy_label('delete'),
+        REPLICAS_POLICY_DEFAULT_RULE,
+        "Delete Replica",
+        [
+            {
+                "path": "/replicas/{replica_id}",
+                "method": "DELETE"
+            }
+        ]
+    )
+]
+
+
+def list_rules():
+    return REPLICAS_POLICY_DEFAULT_RULES

+ 85 - 0
coriolis/policy.py

@@ -0,0 +1,85 @@
+# Copyright 2018 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import itertools
+
+from oslo_config import cfg as conf
+from oslo_log import log as logging
+from oslo_policy import policy
+
+from coriolis import exception
+from coriolis import utils
+from coriolis.policies import base
+from coriolis.policies import endpoints
+from coriolis.policies import general
+from coriolis.policies import migrations
+from coriolis.policies import replicas
+from coriolis.policies import replica_schedules
+from coriolis.policies import replica_tasks_executions
+
+
+LOG = logging.getLogger(__name__)
+
+CONF = conf.CONF
+_ENFORCER = None
+
+DEFAULT_POLICIES_MODULES = [
+    base, endpoints, general, migrations, replicas, replica_schedules,
+    replica_tasks_executions]
+
+
+def reset():
+    global _ENFORCER
+    if _ENFORCER:
+        _ENFORCER.clear()
+    _ENFORCER = None
+
+
+def init():
+    global _ENFORCER
+    global saved_file_rules
+
+    if not _ENFORCER:
+        _ENFORCER = policy.Enforcer(CONF)
+        register_rules(_ENFORCER)
+        _ENFORCER.load_rules()
+
+
+def register_rules(enforcer):
+    enforcer.register_defaults(itertools.chain(*[
+        m.list_rules() for m in DEFAULT_POLICIES_MODULES]))
+
+
+def get_enforcer():
+    init()
+    return _ENFORCER
+
+
+def check_policy_for_context(
+        context, action, target, exc=None, do_raise=True):
+    """ Checks the validity of the given action of the given target based on
+    set policies.
+    On success, returns a value where bool(val) == True.
+    On failure and if `do_raise` if False, returns False.
+    Raises `exception.PolicyNotAuthorized` or `exc` if the policy is
+    not authorized.
+    """
+    init()
+    credentials = context.to_policy_values()
+    if not exc:
+        exc = exception.PolicyNotAuthorized
+    try:
+        result = _ENFORCER.authorize(
+            action, target, credentials,
+            do_raise=do_raise, exc=exc, action=action)
+    except Exception as ex:
+        LOG.debug(
+            "Policy check for '%(action)s' with target '%(target)s' failed "
+            "with credentials: %(credentials)s.\nException: '%(trace)s'", {
+                'action': action, 'target': target,
+                'credentials': credentials, 'trace':
+                utils.get_exception_details()})
+
+        raise exc(str(ex))
+
+    return result

+ 41 - 0
etc/coriolis/policy.yaml

@@ -0,0 +1,41 @@
+"admin": "role:admin"
+"admin_or_owner": "rule:admin or project_id:%(project_id)s"
+
+"migration:providers:list": "rule:admin_or_owner"
+
+"migration:endpoints:create": "rule:admin_or_owner"
+"migration:endpoints:list": "rule:admin_or_owner"
+"migration:endpoints:show": "rule:admin_or_owner"
+"migration:endpoints:update": "rule:admin_or_owner"
+"migration:endpoints:delete": "rule:admin_or_owner"
+"migration:endpoints:validate_connection": "rule:admin_or_owner"
+"migration:endpoints:list_instances": "rule:admin_or_owner"
+"migration:endpoints:get_instance": "rule:admin_or_owner"
+"migration:endpoints:list_networks": "rule:admin_or_owner"
+"migration:endpoints:list_destination_options": "rule:admin_or_owner"
+
+"migration:migrations:create": "rule:admin_or_owner"
+"migration:migrations:list": "rule:admin_or_owner"
+"migration:migrations:show": "rule:admin_or_owner"
+"migration:migrations:show_execution": "rule:admin_or_owner"
+"migration:migrations:cancel": "rule:admin_or_owner"
+"migration:migrations:delete": "rule:admin_or_owner"
+
+"migration:replicas:create": "rule:admin_or_owner"
+"migration:replicas:list": "rule:admin_or_owner"
+"migration:replicas:show": "rule:admin_or_owner"
+"migration:replicas:show_executions": "rule:admin_or_owner"
+"migration:replicas:delete_disks": "rule:admin_or_owner"
+"migration:replicas:delete": "rule:admin_or_owner"
+
+"migration:replica_executions:create": "rule:admin_or_owner"
+"migration:replica_executions:list": "rule:admin_or_owner"
+"migration:replica_executions:show": "rule:admin_or_owner"
+"migration:replica_executions:cancel": "rule:admin_or_owner"
+"migration:replica_executions:delete": "rule:admin_or_owner"
+
+"migration:replica_schedules:create": "rule:admin_or_owner"
+"migration:replica_schedules:list": "rule:admin_or_owner"
+"migration:replica_schedules:show": "rule:admin_or_owner"
+"migration:replica_schedules:update": "rule:admin_or_owner"
+"migration:replica_schedules:delete": "rule:admin_or_owner"

+ 1 - 0
requirements.txt

@@ -11,6 +11,7 @@ oslo.i18n
 oslo.log
 oslo.messaging
 oslo.middleware
+oslo.policy
 oslo.serialization
 oslo.service>=1.12.0
 oslo.versionedobjects