188 lines
5.5 KiB
Python
188 lines
5.5 KiB
Python
#
|
|
# 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 []
|