Source code for cobbler.modules.authentication.spacewalk

"""
Authentication module that uses Spacewalk's auth system.
Any org_admin or kickstart_admin can get in.
"""

# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: Copyright 2007-2009, Red Hat, Inc and Others
# SPDX-FileCopyrightText: Michael DeHaan <michael.dehaan AT gmail>

from typing import TYPE_CHECKING
from xmlrpc.client import Error, ServerProxy

from cobbler.cexceptions import CX
from cobbler.utils import log_exc

if TYPE_CHECKING:
    from cobbler.api import CobblerAPI


[docs]def register() -> str: """ The mandatory Cobbler module registration hook. """ return "authn"
def __looks_like_a_token(password: str) -> bool: """ What spacewalk sends us could be an internal token or it could be a password if it's long and lowercase hex, it's /likely/ a token, and we should try to treat it as a token first, if not, we should treat it as a password. All of this code is there to avoid extra XMLRPC calls, which are slow. A password gets detected as a token if it is lowercase and under 45 characters. :param password: The password which is possibly a token. :return: True if it is possibly a token or False otherwise. """ return password.lower() == password and len(password) > 45 def __check_auth_token( xmlrpc_client: "ServerProxy", api_handle: "CobblerAPI", username: str, password: str ): """ This checks if the auth token is valid. :param xmlrpc_client: The xmlrpc client to check access for. :param api_handle: The api instance to retrieve settings of. :param username: The username to try. :param password: The password to try. :return: In any error case this will return 0. Otherwise, the return value of the API which should be 1. """ # If the token is not a token this will raise an exception rather than return an integer. try: return xmlrpc_client.auth.checkAuthToken(username, password) except Error: logger = api_handle.logger logger.error("Error while checking authentication token.") log_exc() return False def __check_user_login( xmlrpc_client: ServerProxy, api_handle: "CobblerAPI", user_enabled: bool, username: str, password: str, ) -> bool: """ This actually performs the login to spacewalk. :param xmlrpc_client: The xmlrpc client bound to the target spacewalk instance. :param api_handle: The api instance to retrieve settings of. :param user_enabled: Weather we allow Spacewalk users to log in or not. :param username: The username to log in. :param password: The password to log in. :return: True if users are allowed to log in and he is of the role ``config_admin`` or ``org_admin``. """ logger = api_handle.logger try: session = xmlrpc_client.auth.login(username, password) # login success by username, role must also match and user_enabled needs to be true. roles = xmlrpc_client.user.listRoles(session, username) if not isinstance(roles, list): # FIXME: Double check what the actual API is for this! logger.warning("Uyuni/SUMA returned roles not as a list!") return False if user_enabled and ("config_admin" in roles or "org_admin" in roles): return True except Error: logger.error("Error while checking user authentication data.") log_exc() return False
[docs]def authenticate(api_handle: "CobblerAPI", username: str, password: str) -> bool: # pylint: disable=line-too-long """ Validate a username/password combo. This will pass the username and password back to Spacewalk to see if this authentication request is valid. See also: https://github.com/uyuni-project/uyuni/blob/c9b7285117822af96c223cb0b6e0ae96ec7f0837/java/code/src/com/redhat/rhn/frontend/xmlrpc/auth/AuthHandler.java#L107 :param api_handle: The api instance to retrieve settings of. :param username: The username to authenticate against spacewalk/uyuni/SUSE Manager :param password: The password to authenticate against spacewalk/uyuni/SUSE Manager :return: True if it succeeded, False otherwise. :raises CX: Raised in case ``api_handle`` is missing. """ # pylint: enable=line-too-long if api_handle is None: # type: ignore[reportUnnecessaryComparison] raise CX("api_handle required. Please don't call this without it.") server = "https://" + api_handle.settings().redhat_management_server if api_handle.settings().uyuni_authentication_endpoint: server = api_handle.settings().uyuni_authentication_endpoint user_enabled = api_handle.settings().redhat_management_permissive spacewalk_url = f"{server}/rpc/api" with ServerProxy(spacewalk_url, verbose=True) as client: if username == "taskomatic_user" or __looks_like_a_token(password): # The tokens are lowercase hex, but a password can also be lowercase hex, so we have to try it as both a # token and then a password if we are unsure. We do it this way to be faster but also to avoid any login # failed stuff in the logs that we don't need to send. # Problem at this point, 0xdeadbeef is valid as a token but if that fails, it's also a valid password, so we # must try auth system #2 if __check_auth_token(client, api_handle, username, password) != 1: return __check_user_login( client, api_handle, user_enabled, username, password ) return True # It's an older version of spacewalk, so just try the username/pass. # OR: We know for sure it's not a token because it's not lowercase hex. return __check_user_login(client, api_handle, user_enabled, username, password)