The release of pyjwt 2.0.0 changed the behavior of some functions, which caused errors. Fix the errors, use pyjwt 2.0.0's better handling of JWKS, and pin requirement to 2.X to avoid future potential API breaking changes. Change-Id: Ibef736e0f635dfaf4477cc2a90a22665da9f1959
84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
# Copyright 2019 OpenStack Foundation
|
|
# Copyright 2019 Red Hat, 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 logging
|
|
import re
|
|
import jwt
|
|
|
|
from zuul import exceptions
|
|
import zuul.driver.auth.jwt as auth_jwt
|
|
import zuul.lib.capabilities as cpb
|
|
|
|
|
|
"""AuthN/AuthZ related library, used by zuul-web."""
|
|
|
|
|
|
class AuthenticatorRegistry(object):
|
|
"""Registry of authenticators as they are declared in the configuration"""
|
|
|
|
log = logging.getLogger("Zuul.AuthenticatorRegistry")
|
|
|
|
def __init__(self):
|
|
self.authenticators = {}
|
|
self.default_realm = None
|
|
|
|
def configure(self, config):
|
|
capabilities = {'realms': {}}
|
|
first_realm = None
|
|
for section_name in config.sections():
|
|
auth_match = re.match(r'^auth ([\'\"]?)(.*)(\1)$',
|
|
section_name, re.I)
|
|
if not auth_match:
|
|
continue
|
|
auth_name = auth_match.group(2)
|
|
auth_config = dict(config.items(section_name))
|
|
|
|
if 'driver' not in auth_config:
|
|
raise Exception("Auth driver needed for %s" % auth_name)
|
|
|
|
auth_driver = auth_config['driver']
|
|
try:
|
|
driver = auth_jwt.get_authenticator_by_name(auth_driver)
|
|
except IndexError:
|
|
raise Exception(
|
|
"Unknown driver %s for auth %s" % (auth_driver,
|
|
auth_name))
|
|
# TODO catch config specific errors (missing fields)
|
|
self.authenticators[auth_name] = driver(**auth_config)
|
|
caps = self.authenticators[auth_name].get_capabilities()
|
|
# TODO there should be a bijective relationship between realms and
|
|
# authenticators. This should be enforced at config parsing.
|
|
capabilities['realms'].update(caps)
|
|
if first_realm is None:
|
|
first_realm = auth_config.get('realm', None)
|
|
if auth_config.get('default', 'false').lower() == 'true':
|
|
self.default_realm = auth_config.get('realm', 'DEFAULT')
|
|
# do we have any auth defined ?
|
|
if len(capabilities['realms'].keys()) > 0:
|
|
if self.default_realm is None:
|
|
# pick arbitrarily the first defined realm
|
|
self.default_realm = first_realm
|
|
capabilities['default_realm'] = self.default_realm
|
|
cpb.capabilities_registry.register_capabilities('auth', capabilities)
|
|
|
|
def authenticate(self, rawToken):
|
|
unverified = jwt.decode(rawToken, options={'verify_signature': False})
|
|
for auth_name in self.authenticators:
|
|
authenticator = self.authenticators[auth_name]
|
|
if authenticator.issuer_id == unverified.get('iss', ''):
|
|
return authenticator.authenticate(rawToken)
|
|
# No known issuer found, use default realm
|
|
raise exceptions.IssuerUnknownError(self.default_realm)
|