# -*- coding: utf-8 -*- # Copyright 2014 Mirantis, Inc. # # 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 Cookie import re import six from nailgun.api.v1 import urls as api_urls from nailgun.fake_keystone import validate_token from nailgun.settings import settings from keystonemiddleware import auth_token def public_urls(): urls = {} for url, methods in six.iteritems(api_urls.public_urls()): urls['{0}{1}'.format('/api/v1', url)] = methods urls['{0}{1}'.format('/api', url)] = methods urls["/$"] = ['GET'] urls["/static"] = ['GET'] urls["/keystone"] = ['GET', 'POST'] return urls class CookieTokenMixin(object): """Mixin for getting the auth token out of request Token is taken from X-Auth-Token header or, for a particular set of urls, from a cookie. """ cookie_routes_regexs = [re.compile(r) for r in api_urls.cookie_urls()] def get_auth_token(self, env): token = env.get('HTTP_X_AUTH_TOKEN', '') if token: return token path = env.get('PATH_INFO', '/') if any(r.match(path) for r in self.cookie_routes_regexs): c = Cookie.SimpleCookie(env.get('HTTP_COOKIE', '')) token = c.get('token') if token: return token.value return '' class SkipAuthMixin(object): """Skips verification of authentication tokens for public routes in API.""" def __init__(self, app): self.public_api_routes = {} self.app = app try: for route_tpl, methods in six.iteritems(public_urls()): self.public_api_routes[re.compile(route_tpl)] = methods except re.error as e: msg = 'Cannot compile public API routes: {0}'.format(e) raise Exception(msg) super(SkipAuthMixin, self).__init__(app, settings.AUTH) def __call__(self, env, start_response): path = env.get('PATH_INFO', '/') method = env.get('REQUEST_METHOD') # The information whether the API call is being performed against the # public API may be useful. Saving it to the # WSGI environment is reasonable thereby. env['is_public_api'] = False for pattern, methods in six.iteritems(self.public_api_routes): if re.match(pattern, path): if method in methods: env['is_public_api'] = True break if env['is_public_api']: return self.app(env, start_response) return super(SkipAuthMixin, self).__call__(env, start_response) class FakeAuthProtocol(CookieTokenMixin): """Auth protocol for fake mode.""" def __init__(self, app, conf): self.app = app def __call__(self, env, start_response): if validate_token(self.get_auth_token(env)): env['HTTP_X_ROLES'] = settings.FAKE_KEYSTONE_ROLE return self.app(env, start_response) else: start_response('401 Unauthorized', []) return [''] class NailgunKeystoneAuthMiddleware( CookieTokenMixin, SkipAuthMixin, auth_token.AuthProtocol): """Auth middleware for keystone.""" def __call__(self, env, start_response): token = self.get_auth_token(env) if token: env['HTTP_X_AUTH_TOKEN'] = token return super( NailgunKeystoneAuthMiddleware, self ).__call__(env, start_response) class NailgunFakeKeystoneAuthMiddleware(SkipAuthMixin, FakeAuthProtocol): """Auth middleware for fake mode.""" pass