summaryrefslogtreecommitdiffstats
path: root/api/escalator/api
diff options
context:
space:
mode:
Diffstat (limited to 'api/escalator/api')
-rw-r--r--api/escalator/api/__init__.py20
-rw-r--r--api/escalator/api/middleware/__init__.py0
-rw-r--r--api/escalator/api/middleware/context.py137
-rw-r--r--api/escalator/api/policy.py97
-rw-r--r--api/escalator/api/v1/__init__.py14
-rw-r--r--api/escalator/api/v1/router.py25
-rw-r--r--api/escalator/api/versions.py78
7 files changed, 371 insertions, 0 deletions
diff --git a/api/escalator/api/__init__.py b/api/escalator/api/__init__.py
new file mode 100644
index 0000000..e7ebaab
--- /dev/null
+++ b/api/escalator/api/__init__.py
@@ -0,0 +1,20 @@
+# Copyright 2011-2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import paste.urlmap
+
+
+def root_app_factory(loader, global_conf, **local_conf):
+ return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)
diff --git a/api/escalator/api/middleware/__init__.py b/api/escalator/api/middleware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/api/escalator/api/middleware/__init__.py
diff --git a/api/escalator/api/middleware/context.py b/api/escalator/api/middleware/context.py
new file mode 100644
index 0000000..b921289
--- /dev/null
+++ b/api/escalator/api/middleware/context.py
@@ -0,0 +1,137 @@
+# Copyright 2016 OPNFV Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils
+from oslo_config import cfg
+from oslo_log import log as logging
+import webob.exc
+
+from escalator.api import policy
+from escalator.common import wsgi
+import escalator.context
+from escalator import i18n
+
+_ = i18n._
+
+context_opts = [
+ cfg.BoolOpt('owner_is_tenant', default=True,
+ help=_('When true, this option sets the owner of an image '
+ 'to be the tenant. Otherwise, the owner of the '
+ ' image will be the authenticated user issuing the '
+ 'request.')),
+ cfg.StrOpt('admin_role', default='admin',
+ help=_('Role used to identify an authenticated user as '
+ 'administrator.')),
+ cfg.BoolOpt('allow_anonymous_access', default=False,
+ help=_('Allow unauthenticated users to access the API with '
+ 'read-only privileges. This only applies when using '
+ 'ContextMiddleware.')),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(context_opts)
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseContextMiddleware(wsgi.Middleware):
+ def process_response(self, resp):
+ try:
+ request_id = resp.request.context.request_id
+ except AttributeError:
+ LOG.warn(_('Unable to retrieve request id from context'))
+ else:
+ resp.headers['x-openstack-request-id'] = 'req-%s' % request_id
+ return resp
+
+
+class ContextMiddleware(BaseContextMiddleware):
+ def __init__(self, app):
+ self.policy_enforcer = policy.Enforcer()
+ super(ContextMiddleware, self).__init__(app)
+
+ def process_request(self, req):
+ """Convert authentication information into a request context
+
+ Generate a escalator.context.RequestContext object from the available
+ authentication headers and store on the 'context' attribute
+ of the req object.
+
+ :param req: wsgi request object that will be given the context object
+ :raises webob.exc.HTTPUnauthorized: when value of the X-Identity-Status
+ header is not 'Confirmed' and
+ anonymous access is disallowed
+ """
+ if req.headers.get('X-Identity-Status') == 'Confirmed':
+ req.context = self._get_authenticated_context(req)
+ elif CONF.allow_anonymous_access:
+ req.context = self._get_anonymous_context()
+ else:
+ raise webob.exc.HTTPUnauthorized()
+
+ def _get_anonymous_context(self):
+ kwargs = {
+ 'user': None,
+ 'tenant': None,
+ 'roles': [],
+ 'is_admin': False,
+ 'read_only': True,
+ 'policy_enforcer': self.policy_enforcer,
+ }
+ return escalator.context.RequestContext(**kwargs)
+
+ def _get_authenticated_context(self, req):
+ # NOTE(bcwaldon): X-Roles is a csv string, but we need to parse
+ # it into a list to be useful
+ roles_header = req.headers.get('X-Roles', '')
+ roles = [r.strip().lower() for r in roles_header.split(',')]
+
+ # NOTE(bcwaldon): This header is deprecated in favor of X-Auth-Token
+ deprecated_token = req.headers.get('X-Storage-Token')
+
+ service_catalog = None
+ if req.headers.get('X-Service-Catalog') is not None:
+ try:
+ catalog_header = req.headers.get('X-Service-Catalog')
+ service_catalog = jsonutils.loads(catalog_header)
+ except ValueError:
+ raise webob.exc.HTTPInternalServerError(
+ _('Invalid service catalog json.'))
+
+ kwargs = {
+ 'user': req.headers.get('X-User-Id'),
+ 'tenant': req.headers.get('X-Tenant-Id'),
+ 'roles': roles,
+ 'is_admin': CONF.admin_role.strip().lower() in roles,
+ 'auth_token': req.headers.get('X-Auth-Token', deprecated_token),
+ 'owner_is_tenant': CONF.owner_is_tenant,
+ 'service_catalog': service_catalog,
+ 'policy_enforcer': self.policy_enforcer,
+ }
+
+ return escalator.context.RequestContext(**kwargs)
+
+
+class UnauthenticatedContextMiddleware(BaseContextMiddleware):
+ def process_request(self, req):
+ """Create a context without an authorized user."""
+ kwargs = {
+ 'user': None,
+ 'tenant': None,
+ 'roles': [],
+ 'is_admin': True,
+ }
+
+ req.context = escalator.context.RequestContext(**kwargs)
diff --git a/api/escalator/api/policy.py b/api/escalator/api/policy.py
new file mode 100644
index 0000000..4d94f51
--- /dev/null
+++ b/api/escalator/api/policy.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2011 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Policy Engine For Escalator"""
+
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_policy import policy
+
+from escalator.common import exception
+from escalator import i18n
+
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+
+DEFAULT_RULES = policy.Rules.from_dict({
+ 'context_is_admin': 'role:admin',
+ 'default': '@',
+ 'manage_image_cache': 'role:admin',
+})
+
+_ = i18n._
+_LI = i18n._LI
+_LW = i18n._LW
+
+
+class Enforcer(policy.Enforcer):
+ """Responsible for loading and enforcing rules"""
+
+ def __init__(self):
+ if CONF.find_file(CONF.oslo_policy.policy_file):
+ kwargs = dict(rules=None, use_conf=True)
+ else:
+ kwargs = dict(rules=DEFAULT_RULES, use_conf=False)
+ super(Enforcer, self).__init__(CONF, overwrite=False, **kwargs)
+
+ def add_rules(self, rules):
+ """Add new rules to the Rules object"""
+ self.set_rules(rules, overwrite=False, use_conf=self.use_conf)
+
+ def enforce(self, context, action, target):
+ """Verifies that the action is valid on the target in this context.
+
+ :param context: Escalator request context
+ :param action: String representing the action to be checked
+ :param target: Dictionary representing the object of the action.
+ :raises: `escalator.common.exception.Forbidden`
+ :returns: A non-False value if access is allowed.
+ """
+ credentials = {
+ 'roles': context.roles,
+ 'user': context.user,
+ 'tenant': context.tenant,
+ }
+ return super(Enforcer, self).enforce(action, target, credentials,
+ do_raise=True,
+ exc=exception.Forbidden,
+ action=action)
+
+ def check(self, context, action, target):
+ """Verifies that the action is valid on the target in this context.
+
+ :param context: Escalator request context
+ :param action: String representing the action to be checked
+ :param target: Dictionary representing the object of the action.
+ :returns: A non-False value if access is allowed.
+ """
+ credentials = {
+ 'roles': context.roles,
+ 'user': context.user,
+ 'tenant': context.tenant,
+ }
+ return super(Enforcer, self).enforce(action, target, credentials)
+
+ def check_is_admin(self, context):
+ """Check if the given context is associated with an admin role,
+ as defined via the 'context_is_admin' RBAC rule.
+
+ :param context: Escalator request context
+ :returns: A non-False value if context role is admin.
+ """
+ return self.check(context, 'context_is_admin', context.to_dict())
diff --git a/api/escalator/api/v1/__init__.py b/api/escalator/api/v1/__init__.py
new file mode 100644
index 0000000..31285c4
--- /dev/null
+++ b/api/escalator/api/v1/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2011 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
diff --git a/api/escalator/api/v1/router.py b/api/escalator/api/v1/router.py
new file mode 100644
index 0000000..54b09c4
--- /dev/null
+++ b/api/escalator/api/v1/router.py
@@ -0,0 +1,25 @@
+# Copyright 2011 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from escalator.common import wsgi
+
+
+class API(wsgi.Router):
+
+ """WSGI router for Escalator v1 API requests."""
+
+ def __init__(self, mapper):
+ wsgi.Resource(wsgi.RejectMethodController())
+
+ super(API, self).__init__(mapper)
diff --git a/api/escalator/api/versions.py b/api/escalator/api/versions.py
new file mode 100644
index 0000000..751fc76
--- /dev/null
+++ b/api/escalator/api/versions.py
@@ -0,0 +1,78 @@
+# Copyright 2012 OpenStack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import httplib
+
+from oslo_serialization import jsonutils
+from oslo_config import cfg
+import webob.dec
+
+from escalator.common import wsgi
+from escalator import i18n
+
+_ = i18n._
+
+versions_opts = [
+ cfg.StrOpt('public_endpoint', default=None,
+ help=_('Public url to use for versions endpoint. The default '
+ 'is None, which will use the request\'s host_url '
+ 'attribute to populate the URL base. If Escalator is '
+ 'operating behind a proxy, you will want to change '
+ 'this to represent the proxy\'s URL.')),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(versions_opts)
+
+
+class Controller(object):
+
+ """A wsgi controller that reports which API versions are supported."""
+
+ def index(self, req):
+ """Respond to a request for all OpenStack API versions."""
+ def build_version_object(version, path, status):
+ url = CONF.public_endpoint or req.host_url
+ return {
+ 'id': 'v%s' % version,
+ 'status': status,
+ 'links': [
+ {
+ 'rel': 'self',
+ 'href': '%s/%s/' % (url, path),
+ },
+ ],
+ }
+
+ version_objs = []
+ if CONF.enable_v1_api:
+ version_objs.extend([
+ build_version_object(1.1, 'v1', 'SUPPORTED'),
+ build_version_object(1.0, 'v1', 'SUPPORTED'),
+ ])
+
+ response = webob.Response(request=req,
+ status=httplib.MULTIPLE_CHOICES,
+ content_type='application/json')
+ response.body = jsonutils.dumps(dict(versions=version_objs))
+ return response
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ return self.index(req)
+
+
+def create_resource(conf):
+ return wsgi.Resource(Controller())