224 lines
7.7 KiB
Python
224 lines
7.7 KiB
Python
# 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 pecan
|
|
from urllib import parse
|
|
|
|
from barbican.api import controllers
|
|
from barbican.api.controllers import containers
|
|
from barbican.api.controllers import orders
|
|
from barbican.api.controllers import quotas
|
|
from barbican.api.controllers import secrets
|
|
from barbican.api.controllers import secretstores
|
|
from barbican.api.controllers import transportkeys
|
|
from barbican.common import utils
|
|
from barbican import i18n as u
|
|
from barbican import version
|
|
|
|
LOG = utils.getLogger(__name__)
|
|
|
|
_MIN_MICROVERSION = 0
|
|
_MAX_MICROVERSION = 1
|
|
_LAST_UPDATED = '2021-02-10T00:00:00Z'
|
|
|
|
# NOTE(xek): The above defines the minimum and maximum version of the API
|
|
# across all of the v1 REST API.
|
|
# When introducing a new microversion, the _MAX_MICROVERSION
|
|
# needs to be incremented by 1 and the _LAST_UPDATED string updated.
|
|
# The following is the complete (ordered) list of supported versions
|
|
# used by the microversion middleware to parse what is allowed and
|
|
# supported.
|
|
|
|
VERSIONS = ['1.{}'.format(v) for v in range(_MIN_MICROVERSION,
|
|
_MAX_MICROVERSION + 1)]
|
|
MIN_API_VERSION = VERSIONS[0]
|
|
MAX_API_VERSION = VERSIONS[-1]
|
|
|
|
MIME_TYPE_JSON = 'application/json'
|
|
MIME_TYPE_JSON_HOME = 'application/json-home'
|
|
MEDIA_TYPE_JSON = 'application/vnd.openstack.key-manager-%s+json'
|
|
|
|
|
|
def is_supported(req, min_version=MIN_API_VERSION,
|
|
max_version=MAX_API_VERSION):
|
|
"""Check if API request version satisfies version restrictions.
|
|
|
|
:param req: request object
|
|
:param min_version: minimal version of API needed for correct
|
|
request processing
|
|
:param max_version: maximum version of API needed for correct
|
|
request processing
|
|
|
|
:returns: True if request satisfies minimal and maximum API version
|
|
requirements. False in other case.
|
|
"""
|
|
requested_version = str(req.environ.get('key-manager.microversion',
|
|
MIN_API_VERSION))
|
|
|
|
return (VERSIONS.index(max_version) >=
|
|
VERSIONS.index(requested_version) >=
|
|
VERSIONS.index(min_version))
|
|
|
|
|
|
def _version_not_found():
|
|
"""Throw exception indicating version not found."""
|
|
pecan.abort(404, u._("The version you requested wasn't found"))
|
|
|
|
|
|
def _get_versioned_url(version):
|
|
if version[-1] != '/':
|
|
version += '/'
|
|
# If host_href is not set in barbican conf, then derive it from request url
|
|
host_part = utils.get_base_url_from_request()
|
|
if host_part[-1] != '/':
|
|
host_part += '/'
|
|
return parse.urljoin(host_part, version)
|
|
|
|
|
|
class BaseVersionController(object):
|
|
"""Base class for the version-specific controllers"""
|
|
|
|
@classmethod
|
|
def get_version_info(cls, microversion_spec=True):
|
|
version = {
|
|
'id': cls.version_id,
|
|
'status': 'CURRENT',
|
|
'min_version': cls.min_version,
|
|
'max_version': cls.version,
|
|
'links': [
|
|
{
|
|
'rel': 'self',
|
|
'href': _get_versioned_url(cls.version_string),
|
|
},
|
|
{
|
|
'rel': 'describedby',
|
|
'type': 'text/html',
|
|
'href': 'https://docs.openstack.org/'
|
|
}
|
|
],
|
|
}
|
|
if not microversion_spec:
|
|
version.pop('min_version')
|
|
version.pop('max_version')
|
|
version['status'] = 'stable'
|
|
version['updated']: cls.last_updated
|
|
version['media-types'] = [
|
|
{
|
|
'base': MIME_TYPE_JSON,
|
|
'type': MEDIA_TYPE_JSON % cls.version_string
|
|
}
|
|
]
|
|
return version
|
|
|
|
|
|
class V1Controller(BaseVersionController):
|
|
"""Root controller for the v1 API"""
|
|
|
|
version_string = 'v1'
|
|
|
|
# NOTE(jaosorior): We might start using decimals in the future, meanwhile
|
|
# this is the same as the version string.
|
|
version_id = 'v1'
|
|
|
|
version = MAX_API_VERSION
|
|
min_version = MIN_API_VERSION
|
|
last_updated = _LAST_UPDATED
|
|
|
|
def __init__(self):
|
|
LOG.debug('=== Creating V1Controller ===')
|
|
self.secrets = secrets.SecretsController()
|
|
self.orders = orders.OrdersController()
|
|
self.containers = containers.ContainersController()
|
|
self.transport_keys = transportkeys.TransportKeysController()
|
|
self.quotas = quotas.QuotasController()
|
|
setattr(self, 'project-quotas', quotas.ProjectsQuotasController())
|
|
setattr(self, 'secret-stores', secretstores.SecretStoresController())
|
|
|
|
@pecan.expose(generic=True)
|
|
def index(self):
|
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
|
|
@index.when(method='GET', template='json')
|
|
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
|
@controllers.handle_exceptions(u._('Version retrieval'))
|
|
def on_get(self):
|
|
pecan.core.override_template('json')
|
|
if is_supported(pecan.request, max_version='1.0'):
|
|
return {'version': self.get_version_info(microversion_spec=False)}
|
|
else:
|
|
return {'version': self.get_version_info()}
|
|
|
|
|
|
AVAILABLE_VERSIONS = {
|
|
V1Controller.version_string: V1Controller,
|
|
}
|
|
|
|
DEFAULT_VERSION = V1Controller.version_string
|
|
|
|
|
|
class VersionsController(object):
|
|
|
|
def __init__(self):
|
|
LOG.debug('=== Creating VersionsController ===')
|
|
|
|
@pecan.expose(generic=True)
|
|
def index(self, **kwargs):
|
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
|
|
@index.when(method='GET', template='json')
|
|
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
|
def on_get(self, **kwargs):
|
|
"""The list of versions is dependent on the context."""
|
|
self._redirect_to_default_json_home_if_needed(pecan.request)
|
|
|
|
if 'build' in kwargs:
|
|
return {'build': version.__version__}
|
|
|
|
if is_supported(pecan.request, max_version='1.0'):
|
|
resp = {
|
|
'versions': {
|
|
'values': [
|
|
V1Controller.get_version_info(microversion_spec=False)
|
|
]
|
|
}
|
|
}
|
|
else:
|
|
resp = {
|
|
'versions': [
|
|
version_cls.get_version_info() for version_cls in
|
|
AVAILABLE_VERSIONS.values()]
|
|
}
|
|
|
|
# Since we are returning all the versions available, the proper status
|
|
# code is Multiple Choices (300)
|
|
pecan.response.status = 300
|
|
return resp
|
|
|
|
def _redirect_to_default_json_home_if_needed(self, request):
|
|
if self._mime_best_match(request.accept) == MIME_TYPE_JSON_HOME:
|
|
url = _get_versioned_url(DEFAULT_VERSION)
|
|
LOG.debug("Redirecting Request to " + url)
|
|
# NOTE(jaosorior): This issues an "external" redirect because of
|
|
# two reasons:
|
|
# * This module doesn't require authorization, and accessing
|
|
# specific version info needs that.
|
|
# * The resource is a separate app_factory and won't be found
|
|
# internally
|
|
pecan.redirect(url, request=request)
|
|
|
|
def _mime_best_match(self, accept):
|
|
if not accept:
|
|
return MIME_TYPE_JSON
|
|
|
|
SUPPORTED_TYPES = [MIME_TYPE_JSON, MIME_TYPE_JSON_HOME]
|
|
return accept.best_match(SUPPORTED_TYPES)
|