377 lines
10 KiB
Python
377 lines
10 KiB
Python
#
|
|
# DBus structures for describing the user.
|
|
#
|
|
# Copyright (C) 2019 Red Hat, Inc. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
from dasbus.structure import DBusData
|
|
|
|
from dasbus.structure import generate_string_from_data
|
|
from dasbus.typing import * # pylint: disable=wildcard-import
|
|
|
|
from pyanaconda.core.constants import ID_MODE_USE_DEFAULT, ID_MODE_USE_VALUE
|
|
|
|
__all__ = ["UserData"]
|
|
|
|
|
|
class UserData(DBusData):
|
|
"""User data."""
|
|
|
|
def __init__(self):
|
|
self._name = ""
|
|
self._uid = 0
|
|
self._uid_mode = ID_MODE_USE_DEFAULT
|
|
self._groups = list()
|
|
self._gid = 0
|
|
self._gid_mode = ID_MODE_USE_DEFAULT
|
|
self._homedir = ""
|
|
self._password = ""
|
|
self._is_crypted = True
|
|
self._lock = False
|
|
self._shell = ""
|
|
self._gecos = ""
|
|
|
|
@property
|
|
def name(self) -> Str:
|
|
"""Username."
|
|
|
|
For example: 'user'
|
|
|
|
Should comply with the usual limitations for Linux usernames.
|
|
|
|
:return: a username
|
|
:rtype: str
|
|
"""
|
|
return self._name
|
|
|
|
@name.setter
|
|
def name(self, name: Str):
|
|
self._name = name
|
|
|
|
@property
|
|
def uid_mode(self) -> Str:
|
|
"""Mode of UID.
|
|
|
|
Contains a string describing the mode of the user's UID: Use the value or default.
|
|
|
|
Possible values are:
|
|
- "ID_MODE_USE_VALUE"
|
|
- "ID_MODE_USE_DEFAULT"
|
|
|
|
:return: the mode
|
|
:rtype str:
|
|
"""
|
|
return self._uid_mode
|
|
|
|
@uid_mode.setter
|
|
def uid_mode(self, status: Str):
|
|
self._uid_mode = status
|
|
|
|
@property
|
|
def uid(self) -> UInt32:
|
|
"""The users UID.
|
|
|
|
If ignored due to uid_mode, this defaults to the next available non-system UID.
|
|
|
|
For example: 1234
|
|
|
|
:return: user's UID
|
|
:rtype: int
|
|
"""
|
|
return self._uid
|
|
|
|
@uid.setter
|
|
def uid(self, uid: UInt32):
|
|
self._uid = uid
|
|
|
|
def get_uid(self):
|
|
"""Return a UID value which can be a number or None.
|
|
|
|
Prefer using this method instead of directly reading uid and uid_mode.
|
|
|
|
:return: UID or None if not set
|
|
:rtype: int or None
|
|
"""
|
|
if self._uid_mode == ID_MODE_USE_DEFAULT:
|
|
return None
|
|
else:
|
|
return self._uid
|
|
|
|
def set_uid(self, new_uid):
|
|
"""Set UID value and mode from a value which can be None.
|
|
|
|
Prefer using this method instead of directly setting uid and uid_mode.
|
|
|
|
:param new_uid: new UID
|
|
:type new_uid: int or None
|
|
"""
|
|
if new_uid is not None:
|
|
self._uid = new_uid
|
|
self._uid_mode = ID_MODE_USE_VALUE
|
|
else:
|
|
self._uid = 0
|
|
self._uid_mode = ID_MODE_USE_DEFAULT
|
|
|
|
@property
|
|
def groups(self) -> List[Str]:
|
|
"""List of additional groups (in additional to the default group named after
|
|
the user) the user should be a member of. Any groups that do not already
|
|
exist will be created.
|
|
|
|
For example: ['mock', 'dialout']
|
|
|
|
:return: a list of groups the user should be a member of
|
|
:rtype: a list of str
|
|
"""
|
|
return self._groups
|
|
|
|
@groups.setter
|
|
def groups(self, groups: List[Str]):
|
|
self._groups = groups
|
|
|
|
@property
|
|
def gid_mode(self) -> Str:
|
|
"""Mode of GID.
|
|
|
|
Contains a string describing the mode of the user's GID: Use the value or default.
|
|
|
|
Possible values are:
|
|
- "ID_MODE_USE_VALUE"
|
|
- "ID_MODE_USE_DEFAULT"
|
|
|
|
:return: the mode
|
|
:rtype str:
|
|
"""
|
|
return self._gid_mode
|
|
|
|
@gid_mode.setter
|
|
def gid_mode(self, status: Str):
|
|
self._gid_mode = status
|
|
|
|
@property
|
|
def gid(self) -> UInt32:
|
|
"""The GID of the user's primary group.
|
|
|
|
If ignored due to gid_mode, defaults to the next available non-system GID.
|
|
|
|
For example: 1234
|
|
|
|
:return: primary group GID
|
|
:rtype: int
|
|
"""
|
|
return self._gid
|
|
|
|
@gid.setter
|
|
def gid(self, gid: UInt32):
|
|
self._gid = gid
|
|
|
|
def get_gid(self):
|
|
"""Return a GID value which can be a number or None.
|
|
|
|
Prefer using this method instead of directly reading gid and gid_mode.
|
|
|
|
:return: GID or None if not set
|
|
:rtype: int or None
|
|
"""
|
|
if self._gid_mode == ID_MODE_USE_DEFAULT:
|
|
return None
|
|
else:
|
|
return self._gid
|
|
|
|
def set_gid(self, new_gid):
|
|
"""Set GID value and mode from a value which can be None.
|
|
|
|
Prefer using this method instead of directly writing gid and gid_mode.
|
|
|
|
:param new_gid: new GID
|
|
:type new_gid: int or None
|
|
"""
|
|
if new_gid is not None:
|
|
self._gid = new_gid
|
|
self._gid_mode = ID_MODE_USE_VALUE
|
|
else:
|
|
self._gid = 0
|
|
self._gid_mode = ID_MODE_USE_DEFAULT
|
|
|
|
@property
|
|
def homedir(self) -> Str:
|
|
"""The home directory of the user.
|
|
|
|
If not provided, this defaults to /home/.
|
|
|
|
For example: 'user_home'
|
|
|
|
:return: home directory of the user
|
|
:rtype: str
|
|
"""
|
|
return self._homedir
|
|
|
|
@homedir.setter
|
|
def homedir(self, homedir: Str):
|
|
self._homedir = homedir
|
|
|
|
@property
|
|
def password(self) -> Str:
|
|
"""The user password.
|
|
|
|
If not provided, the account will be locked by default. If this is set,
|
|
the password argument is assumed to already be encrypted.
|
|
|
|
If the plaintext property has been set, it has the opposite effect,
|
|
the user password is assumed to not be encrypted.
|
|
|
|
To create an encrypted password you can use python:
|
|
|
|
python3 -c 'import crypt; print(crypt.crypt("My Password", "$y$j9T$My Sault"))'
|
|
|
|
This will compute a hash of your password using the yescrypt hasing method and
|
|
your provided salt.
|
|
|
|
If the yescrypt method is not supported by your system, you can use the
|
|
sha512crypt hashing method:
|
|
|
|
python3 -c 'import crypt; print(crypt.crypt("My Password", "$6$My Sault"))'
|
|
|
|
This will compute a hash of your password using the sha512crypt hasing method
|
|
and your provided salt.
|
|
|
|
For example: "CorrectHorseBatteryStaple"
|
|
|
|
:return: user password
|
|
:rtype: str
|
|
"""
|
|
return self._password
|
|
|
|
@password.setter
|
|
def password(self, password: Str):
|
|
self._password = password
|
|
|
|
@property
|
|
def is_crypted(self) -> Bool:
|
|
"""Reports if the password is already crypted.
|
|
|
|
A password is considered to be crypted by default.
|
|
|
|
For example: True
|
|
|
|
:return: if password is already crypted
|
|
:rtype: bool
|
|
"""
|
|
return self._is_crypted
|
|
|
|
@is_crypted.setter
|
|
def is_crypted(self, is_crypted: Bool):
|
|
self._is_crypted = is_crypted
|
|
|
|
@property
|
|
def lock(self) -> Bool:
|
|
"""If lock is True, this user account will be locked be locked.
|
|
|
|
That is, the user will not be able to login from the console.
|
|
|
|
For example: False
|
|
|
|
:rtype: if the user account should be locked
|
|
:rtype: bool
|
|
"""
|
|
return self._lock
|
|
|
|
@lock.setter
|
|
def lock(self, lock: Bool):
|
|
self._lock = lock
|
|
|
|
@property
|
|
def shell(self) -> Str:
|
|
"""The users login shell.
|
|
|
|
If not provided this defaults to the system default.
|
|
|
|
For example: "/bin/bash"
|
|
|
|
:return: user login shell
|
|
:rtype: str
|
|
"""
|
|
return self._shell
|
|
|
|
@shell.setter
|
|
def shell(self, shell: Str):
|
|
self._shell = shell
|
|
|
|
@property
|
|
def gecos(self) -> Str:
|
|
"""Provides the GECOS information for the user.
|
|
|
|
This is a string of various system-specific fields separated by a comma.
|
|
It is frequently used to specify the user's full name, office number, and the like.
|
|
See man 5 passwd for more details.
|
|
|
|
For examples: "foo"
|
|
|
|
:return: GECOS information for the user
|
|
:rtype: str
|
|
"""
|
|
return self._gecos
|
|
|
|
@gecos.setter
|
|
def gecos(self, gecos: Str):
|
|
self._gecos = gecos
|
|
|
|
def __eq__(self, other_instance):
|
|
"""Compare if this UserData instance is the same as other UserData instance.
|
|
|
|
As we don't really do much validation for the user specifications, including
|
|
duplication checks and will just warn about errors during user creation then
|
|
simply checking the user name seems like a good enough way to tell two users apart.
|
|
|
|
Different settings other than username are just two variants of the same user.
|
|
Two users with different username are two different users.
|
|
|
|
:param other_instance: another UserData instance
|
|
:return: if the other instance seems to be functionally the same as this one
|
|
"""
|
|
return self.name == other_instance.name
|
|
|
|
def __repr__(self):
|
|
"""Describe the user for easy debugging.
|
|
|
|
As there are many fields many of which might not be set,
|
|
we only try to list the values that are set.
|
|
|
|
:return: a string describing the UserData instance
|
|
:rtype: str
|
|
"""
|
|
return generate_string_from_data(
|
|
self, skip=["password"], add={"password_set": bool(self.password)}
|
|
)
|
|
|
|
def has_admin_priviledges(self):
|
|
"""Report if the user described by this structure is an admin.
|
|
|
|
Admin users are members of the "wheel" group.
|
|
|
|
:return: True if user is admin, False otherwise
|
|
:rtype: bool
|
|
"""
|
|
return "wheel" in self._groups
|
|
|
|
def set_admin_priviledges(self, admin):
|
|
"""Set if the user should be an admin.
|
|
|
|
This effectively means adding/removing the "wheel" group from users group list.
|
|
"""
|
|
if admin and "wheel" not in self._groups:
|
|
self._groups.append("wheel")
|
|
elif not admin and "wheel" in self._groups:
|
|
self._groups.remove("wheel")
|