# # DBus structures for secret data. # # Copyright (C) 2020 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # the GNU General Public License v.2, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY expressed or implied, including the implied warranties of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. You should have received a copy of the # GNU General Public License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the # source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc. # import copy from dasbus.structure import DBusData, get_fields, generate_string_from_data from dasbus.typing import * # pylint: disable=wildcard-import from pyanaconda.anaconda_loggers import get_module_logger from pyanaconda.core.constants import SECRET_TYPE_NONE, SECRET_TYPE_TEXT, SECRET_TYPE_HIDDEN log = get_module_logger(__name__) __all__ = ["get_public_copy", "hide_secrets", "SecretData", "SecretDataList"] def get_public_copy(obj: DBusData): """Get a public copy of the given DBus data object. The function will create a copy of the given DBus data object, automatically detect all fields that are instances of SecretData and call their method hide_secret. The final DBus data object is safe to send to a DBus client, because it doesn't contain sensitive information. The automatic detection works only for first-level fields of the type SecretData. Nested types of fields are not supported at this moment. For example, a field of the type List[SecretData] will be ignored. We will also ignore types that are subclasses of DBusData (with the exception of SecretData). FIXME: Add support for nested types of fields. :param obj: a data object with secrets :type obj: an instance of DBusData :return: a copy of data object with hidden secrets :rtype: an instance of DBusData """ obj = copy.deepcopy(obj) hide_secrets(obj) return obj def hide_secrets(obj: DBusData): """Hide all secrets in the given DBus data object. The function will search fields of the given DBus data object, check if the value of a field is an instance of SecretData and call its method hide_secret. :param obj: a data object with secrets :type obj: an instance of DBusData """ fields = get_fields(obj) hidden = [] for field in fields.values(): value = getattr(obj, field.data_name) if isinstance(value, SecretData): value.hide_secret() hidden.append(field.name) if hidden: log.debug("Hiding DBus fields %s.", ", ".join(hidden)) class SecretData(DBusData): """Data for a secret string value.""" def __init__(self): self._type = SECRET_TYPE_NONE self._value = self._get_initial_value() @property def type(self) -> Str: """The type of the secret. Supported values: NONE The secret is not set. HIDDEN The secret is hidden. TEXT The secret is in plain text. :return: a string """ return self._type @type.setter def type(self, value: Str): self._type = value @property def value(self) -> Str: """The value of the secret. The value is set only if the secret is set and not hidden. :return: a string """ return self._value @value.setter def value(self, value: Str): self._value = value def _get_initial_value(self) -> Str: """Get the initial value of the secret. :return: a value """ return "" def set_secret(self, value): """Set the secret. If the value is None, clear the secret. Otherwise, set the secret to the given value. :param value: a value of the secret or None """ if value is None: self.type = SECRET_TYPE_NONE self.value = self._get_initial_value() else: self.type = SECRET_TYPE_TEXT self.value = value def hide_secret(self): """Hide the secret. If the secret is not set, do nothing. Otherwise, hide the value of the secret. """ if self.type == SECRET_TYPE_NONE: self.value = self._get_initial_value() else: self.type = SECRET_TYPE_HIDDEN self.value = self._get_initial_value() def __repr__(self): """Convert this data object to a string.""" return generate_string_from_data( self, skip=["value"], add={"value_set": bool(self.value)} ) class SecretDataList(SecretData): """Data for a secret list of string values.""" @property def value(self) -> List[Str]: """The value of the secret. :return: a list of strings """ return self._value @value.setter def value(self, value: List[Str]): self._value = value def _get_initial_value(self) -> List[Str]: """Get the initial value of the secret. :return: a value """ return []