Update auth middlewares
Change-Id: I53a0b3e1b60ef425be521bcf237b6a8e8af6d755
This commit is contained in:
parent
62986df7d0
commit
c27d49dbbd
|
@ -1,15 +1,20 @@
|
||||||
# Use this pipeline for no auth - DEFAULT
|
# Use this pipeline for trusted auth - DEFAULT
|
||||||
|
# Auth token has format user:tenant:roles
|
||||||
[pipeline:glare-api]
|
[pipeline:glare-api]
|
||||||
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler unauthenticated-context glarev1api
|
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler trustedauth glarev1api
|
||||||
|
|
||||||
# Use this pipeline for keystone auth
|
# Use this pipeline for keystone auth
|
||||||
[pipeline:glare-api-keystone]
|
[pipeline:glare-api-keystone]
|
||||||
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context glarev1api
|
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler authtoken context glarev1api
|
||||||
|
|
||||||
# Use this pipeline for Keycloak auth
|
# Use this pipeline for Keycloak auth
|
||||||
[pipeline:glare-api-keystone]
|
[pipeline:glare-api-keycloak]
|
||||||
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler keycloak context glarev1api
|
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler keycloak context glarev1api
|
||||||
|
|
||||||
|
# Use this pipeline when you want to specify context params manually
|
||||||
|
[pipeline:glare-api-noauth]
|
||||||
|
pipeline = cors faultwrapper healthcheck http_proxy_to_wsgi versionnegotiation osprofiler context glarev1api
|
||||||
|
|
||||||
[app:glarev1api]
|
[app:glarev1api]
|
||||||
paste.app_factory = glare.api.v1.router:API.factory
|
paste.app_factory = glare.api.v1.router:API.factory
|
||||||
|
|
||||||
|
@ -27,8 +32,8 @@ paste.filter_factory = glare.api.middleware.fault:GlareFaultWrapperFilter.factor
|
||||||
[filter:context]
|
[filter:context]
|
||||||
paste.filter_factory = glare.api.middleware.context:ContextMiddleware.factory
|
paste.filter_factory = glare.api.middleware.context:ContextMiddleware.factory
|
||||||
|
|
||||||
[filter:unauthenticated-context]
|
[filter:trustedauth]
|
||||||
paste.filter_factory = glare.api.middleware.context:UnauthenticatedContextMiddleware.factory
|
paste.filter_factory = glare.api.middleware.context:TrustedAuthMiddleware.factory
|
||||||
|
|
||||||
[filter:authtoken]
|
[filter:authtoken]
|
||||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||||
|
|
|
@ -83,15 +83,10 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware):
|
||||||
if req.headers.get('X-Identity-Status') == 'Confirmed':
|
if req.headers.get('X-Identity-Status') == 'Confirmed':
|
||||||
req.context = ContextMiddleware._get_authenticated_context(req)
|
req.context = ContextMiddleware._get_authenticated_context(req)
|
||||||
elif CONF.allow_anonymous_access:
|
elif CONF.allow_anonymous_access:
|
||||||
req.context = ContextMiddleware._get_anonymous_context()
|
req.context = RequestContext(read_only=True, is_admin=False)
|
||||||
else:
|
else:
|
||||||
raise exception.Unauthorized()
|
raise exception.Unauthorized()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_anonymous_context():
|
|
||||||
"""Anonymous user has only Read-Only grants."""
|
|
||||||
return RequestContext(read_only=True, is_admin=False)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_authenticated_context(req):
|
def _get_authenticated_context(req):
|
||||||
headers = req.headers
|
headers = req.headers
|
||||||
|
@ -110,14 +105,31 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware):
|
||||||
return RequestContext.from_environ(req.environ, **kwargs)
|
return RequestContext.from_environ(req.environ, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UnauthenticatedContextMiddleware(base_middleware.ConfigurableMiddleware):
|
class TrustedAuthMiddleware(base_middleware.ConfigurableMiddleware):
|
||||||
"""Process requests and responses when auth is turned off at all."""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_request(req):
|
def process_request(req):
|
||||||
"""Create a context without an authorized user.
|
auth_token = req.headers.get('X-Auth-Token')
|
||||||
|
if not auth_token:
|
||||||
|
msg = _("Auth token must be provided")
|
||||||
|
raise exception.Unauthorized(msg)
|
||||||
|
try:
|
||||||
|
user, tenant, roles = auth_token.split(':')
|
||||||
|
except ValueError:
|
||||||
|
msg = _("Wrong auth token format. It must be 'user:tenant:roles'")
|
||||||
|
raise exception.Unauthorized(msg)
|
||||||
|
if tenant.lower() == 'none':
|
||||||
|
tenant = None
|
||||||
|
req.headers['X-User-Id'] = user
|
||||||
|
req.headers['X-Tenant-Id'] = tenant
|
||||||
|
req.headers['X-Roles'] = roles.split(',')
|
||||||
|
req.headers['X-Identity-Status'] = 'Confirmed'
|
||||||
|
kwargs = {
|
||||||
|
'user': user,
|
||||||
|
'tenant': tenant,
|
||||||
|
'roles': roles,
|
||||||
|
'is_admin': 'admin' in req.headers['X-Roles'],
|
||||||
|
'auth_token': auth_token,
|
||||||
|
}
|
||||||
|
|
||||||
When glare deployed as public repo everybody is admin
|
req.context = context.RequestContext(**kwargs)
|
||||||
without any credentials.
|
|
||||||
"""
|
|
||||||
req.context = RequestContext(is_admin=True)
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import memcache
|
import memcache
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_middleware import base as base_middleware
|
||||||
import pprint
|
import pprint
|
||||||
import requests
|
import requests
|
||||||
import webob.dec
|
import webob.dec
|
||||||
|
@ -53,10 +54,10 @@ CONF = cfg.CONF
|
||||||
CONF.register_opts(keycloak_oidc_opts, group="keycloak_oidc")
|
CONF.register_opts(keycloak_oidc_opts, group="keycloak_oidc")
|
||||||
|
|
||||||
|
|
||||||
class KeycloakAuthMiddleware(object):
|
class KeycloakAuthMiddleware(base_middleware.Middleware):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
super(KeycloakAuthMiddleware, self).__init__(application=app)
|
||||||
mcserv_url = CONF.memcached_server
|
mcserv_url = CONF.keycloak_oidc.memcached_server
|
||||||
self.mcclient = memcache.Client(mcserv_url) if mcserv_url else None
|
self.mcclient = memcache.Client(mcserv_url) if mcserv_url else None
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
|
@ -82,7 +83,7 @@ class KeycloakAuthMiddleware(object):
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
if self.mcclient:
|
if self.mcclient:
|
||||||
self.mcclient.set(access_token, resp.json(),
|
self.mcclient.set(access_token, resp.json(),
|
||||||
time=CONF.token_cache_time)
|
time=CONF.keycloak_oidc.token_cache_time)
|
||||||
info = resp.json()
|
info = resp.json()
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
|
@ -114,7 +115,7 @@ class KeycloakAuthMiddleware(object):
|
||||||
roles = [role['name'] for role in resp.json()]
|
roles = [role['name'] for role in resp.json()]
|
||||||
if self.mcclient:
|
if self.mcclient:
|
||||||
self.mcclient.set(realm_name, roles,
|
self.mcclient.set(realm_name, roles,
|
||||||
time=CONF.token_cache_time)
|
time=CONF.keycloak_oidc.token_cache_time)
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Roles for realm %s: %s" %
|
"Roles for realm %s: %s" %
|
||||||
|
@ -131,4 +132,4 @@ class KeycloakAuthMiddleware(object):
|
||||||
roles = ','.join(self.get_roles(request))
|
roles = ','.join(self.get_roles(request))
|
||||||
request.headers["X-Identity-Status"] = "Confirmed"
|
request.headers["X-Identity-Status"] = "Confirmed"
|
||||||
request.headers["X-Roles"] = roles
|
request.headers["X-Roles"] = roles
|
||||||
return request.get_response(self.app)
|
return request.get_response(self.application)
|
||||||
|
|
|
@ -315,10 +315,7 @@ default_store = %(default_store)s
|
||||||
connection = %(sql_connection)s
|
connection = %(sql_connection)s
|
||||||
"""
|
"""
|
||||||
self.paste_conf_base = """[pipeline:glare-api]
|
self.paste_conf_base = """[pipeline:glare-api]
|
||||||
pipeline = faultwrapper versionnegotiation unauthenticated-context glarev1api
|
pipeline = faultwrapper versionnegotiation trustedauth glarev1api
|
||||||
|
|
||||||
[pipeline:glare-api-fakeauth]
|
|
||||||
pipeline = faultwrapper versionnegotiation fakeauth context glarev1api
|
|
||||||
|
|
||||||
[pipeline:glare-api-noauth]
|
[pipeline:glare-api-noauth]
|
||||||
pipeline = faultwrapper versionnegotiation context glarev1api
|
pipeline = faultwrapper versionnegotiation context glarev1api
|
||||||
|
@ -339,12 +336,9 @@ paste.filter_factory =
|
||||||
[filter:context]
|
[filter:context]
|
||||||
paste.filter_factory = glare.api.middleware.context:ContextMiddleware.factory
|
paste.filter_factory = glare.api.middleware.context:ContextMiddleware.factory
|
||||||
|
|
||||||
[filter:unauthenticated-context]
|
[filter:trustedauth]
|
||||||
paste.filter_factory =
|
paste.filter_factory =
|
||||||
glare.api.middleware.context:UnauthenticatedContextMiddleware.factory
|
glare.api.middleware.context:TrustedAuthMiddleware.factory
|
||||||
|
|
||||||
[filter:fakeauth]
|
|
||||||
paste.filter_factory = glare.tests.utils:FakeAuthMiddleware.factory
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,8 @@ import fixtures
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as cfg_fixture
|
from oslo_config import fixture as cfg_fixture
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_middleware import base as base_middleware
|
|
||||||
import six
|
|
||||||
import testtools
|
import testtools
|
||||||
import webob
|
|
||||||
|
|
||||||
from glare.api.middleware import context
|
|
||||||
from glare.common import config
|
from glare.common import config
|
||||||
from glare.common import utils
|
from glare.common import utils
|
||||||
|
|
||||||
|
@ -371,103 +367,3 @@ def xattr_writes_supported(path):
|
||||||
os.unlink(fake_filepath)
|
os.unlink(fake_filepath)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class FakeAuthMiddleware(base_middleware.ConfigurableMiddleware):
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def process_request(req):
|
|
||||||
auth_token = req.headers.get('X-Auth-Token')
|
|
||||||
user = None
|
|
||||||
tenant = None
|
|
||||||
roles = []
|
|
||||||
if auth_token:
|
|
||||||
user, tenant, role = auth_token.split(':')
|
|
||||||
if tenant.lower() == 'none':
|
|
||||||
tenant = None
|
|
||||||
roles = [role]
|
|
||||||
req.headers['X-User-Id'] = user
|
|
||||||
req.headers['X-Tenant-Id'] = tenant
|
|
||||||
req.headers['X-Roles'] = role
|
|
||||||
req.headers['X-Identity-Status'] = 'Confirmed'
|
|
||||||
kwargs = {
|
|
||||||
'user': user,
|
|
||||||
'tenant': tenant,
|
|
||||||
'roles': roles,
|
|
||||||
'is_admin': False,
|
|
||||||
'auth_token': auth_token,
|
|
||||||
}
|
|
||||||
|
|
||||||
req.context = context.RequestContext(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeHTTPResponse(object):
|
|
||||||
def __init__(self, status=200, headers=None, data=None, *args, **kwargs):
|
|
||||||
data = data or b'I am a teapot, short and stout\n'
|
|
||||||
self.data = six.BytesIO(data)
|
|
||||||
self.read = self.data.read
|
|
||||||
self.status = status
|
|
||||||
self.headers = headers or {'content-length': len(data)}
|
|
||||||
|
|
||||||
def getheader(self, name, default=None):
|
|
||||||
return self.headers.get(name.lower(), default)
|
|
||||||
|
|
||||||
def getheaders(self):
|
|
||||||
return self.headers or {}
|
|
||||||
|
|
||||||
def read(self, amt):
|
|
||||||
self.data.read(amt)
|
|
||||||
|
|
||||||
|
|
||||||
class Httplib2WsgiAdapter(object):
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def request(self, uri, method="GET", body=None, headers=None):
|
|
||||||
req = webob.Request.blank(uri, method=method, headers=headers)
|
|
||||||
req.body = body
|
|
||||||
resp = req.get_response(self.app)
|
|
||||||
return Httplib2WebobResponse(resp), resp.body
|
|
||||||
|
|
||||||
|
|
||||||
class Httplib2WebobResponse(object):
|
|
||||||
def __init__(self, webob_resp):
|
|
||||||
self.webob_resp = webob_resp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self):
|
|
||||||
return self.webob_resp.status_code
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self.webob_resp.headers[key]
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
return self.webob_resp.headers[key]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def allow(self):
|
|
||||||
return self.webob_resp.allow
|
|
||||||
|
|
||||||
@allow.setter
|
|
||||||
def allow(self, allowed):
|
|
||||||
if type(allowed) is not str:
|
|
||||||
raise TypeError('Allow header should be a str')
|
|
||||||
|
|
||||||
self.webob_resp.allow = allowed
|
|
||||||
|
|
||||||
|
|
||||||
class HttplibWsgiAdapter(object):
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
self.req = None
|
|
||||||
|
|
||||||
def request(self, method, url, body=None, headers=None):
|
|
||||||
if headers is None:
|
|
||||||
headers = {}
|
|
||||||
self.req = webob.Request.blank(url, method=method, headers=headers)
|
|
||||||
self.req.body = body
|
|
||||||
|
|
||||||
def getresponse(self):
|
|
||||||
response = self.req.get_response(self.app)
|
|
||||||
return FakeHTTPResponse(response.status_code, response.headers,
|
|
||||||
response.body)
|
|
||||||
|
|
Loading…
Reference in New Issue