Source code for bexchange.auth.keyczarauth

# Copyright (C) 2021- Swedish Meteorological and Hydrological Institute (SMHI)
#
# This file is part of baltrad-exchange.
#
# baltrad-exchange is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# baltrad-exchange is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with baltrad-exchange.  If not, see <http://www.gnu.org/licenses/>.
###############################################################################

## Authentication handling within baltrad-exchange

## @file
## @author Anders Henja, SMHI
## @date 2022-05-02

import os
import logging
from bexchange.auth import coreauth

from baltradcrypto import crypto
from baltradcrypto.crypto import keyczarcrypto

logger = logging.getLogger("bexchange.auth")

[docs] class KeyczarAuth(coreauth.Auth): """Provide authentication through Keyczar registered as *keyczar* in *baltrad.bdbserver.web.auth* entry-point """ def __init__(self, keystore_root): """ :param keystore_root: default path to search keys from """ if not os.path.isabs(keystore_root): raise ValueError("keystore_root must be an absolute path") self._keystore_root = keystore_root self._private_key = None self._verifiers = {}
[docs] def add_key(self, name, path): """ :param name: the name to associate the key with for lookups :param path: an absolute or relative path to the key. :raise: :class:`keyczar.errors.KeyczarError` if the key can not be read creates a :class:`keyczar.keyczar.Verifier` from the key located at *path* """ if not os.path.isabs(path): path = os.path.join(self._keystore_root, path) logger.info("adding key %s from %s", name, path) verifier = keyczarcrypto.keyczar_verifier.read(path) self._verifiers[name] = verifier
[docs] def add_key_config(self, conf): """Adds a key from key config :param conf: The key config :return: the node name this key should be associated with """ try: pubkey = conf["pubkey"] nodename = conf["nodename"] except: raise coreauth.AuthError("Missing pubkey and/or nodename in key config") if not os.path.isabs(pubkey): pubkey = os.path.join(self._keystore_root, pubkey) logger.info("adding key config %s", conf["nodename"]) verifier = keyczarcrypto.keyczar_verifier.read(pubkey) self._verifiers[nodename] = verifier return nodename
[docs] def authenticate(self, req, credentials): """Authenticates the request against the credentials. :param req: The http request :param credentials: The credentials that should be verified against :return: True if authenticated, False otherwise. """ try: keyname, signature = credentials.rsplit(":") except ValueError: raise coreauth.AuthError("invalid credentials: %s" % credentials) try: verifier = self._verifiers[keyname] except KeyError: raise coreauth.AuthError("no verifier for key: %s" % keyname) signed_str = self.create_signable_string(req) try: result = verifier.verify(signed_str, signature) return result except Exception as e: logger.exception("unhandled Exception error %s", e.__str__()) return False
[docs] def create_signable_string(self, req): """construct a signable string from a :class:`~.util.Request` See :ref:`doc-rest-keyczar-authentication` for details. """ fragments = [req.method, req.url] for key in ("content-type", "content-md5", "date"): if key in req.headers: value = req.headers[key].strip() if value: fragments.append(value) return "\n".join(fragments)
[docs] @classmethod def from_conf(cls, conf): """Create from configuration. :param conf: a :class:`~.config.Properties` instance :raise: :class:`LookupError` if a required configuration parameter is missing. All keys are accessed with prefix *baltrad.bdb.server.auth.keyczar.*. The value of `keystore_root` is passed to the constructor. All values under `keys` are passed to :meth:`add_key` where the configuration key is used as a name and the value is used as the path for the key lookup. """ conf = conf.filter("baltrad.exchange.auth.keyczar.") result = KeyczarAuth(conf.get("keystore_root")) keyconf = conf.filter("keys.") for key in keyconf.get_keys(): result.add_key(key, keyconf.get(key)) result._private_key = conf.get("private.key") return result