X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/52ea29609e89bf865ab7fc3df828648dde998f48..10fea9a7ddab6a654922514b13b135772cc98a01:/wizard/user.py diff --git a/wizard/user.py b/wizard/user.py index 81015eb..2d364a4 100644 --- a/wizard/user.py +++ b/wizard/user.py @@ -1,24 +1,155 @@ """ -Module for querying information about users. This mostly asks plugins for -the extra information, and falls back to using a default that should work -on most systems (but by no means all systems.) +Module for querying information about users. This mostly asks plugins +for the extra information, and falls back to using a default that should +work on most systems (but by no means all systems.) """ import pkg_resources import os +import socket +import logging +import pwd + +from wizard import plugin def quota(dir=None): """ - Returns a tuple (quota usage, quota limit). Returns ``(0, None)`` if + Returns a tuple (quota usage, quota limit). Returns ``None`` if the quota usage is unknown. If ``dir`` is omitted, the current working directory is assumed. Value returned is in bytes. + + This function implements a plugin interface named + :ref:`wizard.user.quota`. """ if dir is None: dir = os.getcwd() - unknown = (0, None) - for entry in pkg_resources.iter_entry_points("wizard.user.quota"): - func = entry.load() - r = func(dir) - if r != unknown: - return r - return unknown + return plugin.hook("wizard.user.quota", [dir]) + +def email(name=None): + """ + Converts a username into an email address to that user. If you have + a UID, you will have to convert it into a username first. If no + canonical source of information is found, an heuristic approach + will be used. If ``name`` is ``None``, the current user will be + used unless it is root, in which case :func:`operator` is tried + first to determine the real current user. + + This function implements a plugin interface named + :ref:`wizard.user.email`. + """ + if name is None: + logging.info("wizard.user.email: Determining email for current user") + env_email = os.getenv("EMAIL") + if env_email is not None: + logging.info("wizard.user.email: Used environment email %s", env_email) + return env_email + name = operator() + # run plugins + r = plugin.hook("wizard.user.email", [name]) + if r is not None: + return r + # guess an email + try: + mailname = open("/etc/mailname").read() + except: + mailname = socket.getfqdn() + return name + "@" + mailname + +def operator(): + """ + Determines the username of the true person who is running this + program. If the process's real uid is nonzero, just do a passwd + lookup; otherwise attempt to figure out the user behind the root + prompt some other way. + + This function implements a plugin interface named + :ref:`wizard.user.operator`. + """ + uid = os.getuid() + if uid: + pwdentry = pwd.getpwuid(uid) + return pwdentry.pw_name + # run plugins + r = plugin.hook("wizard.user.operator", []) + if r is not None: + return r + # use SUDO_USER + sudo_user = os.getenv("SUDO_USER") + if not sudo_user: + return None + pwdentry = pwd.getpwnam(sudo_user) + return pwdentry.pw_name + +def passwd(path=None, uid=None): + """ + Returns a passwd-like entry (a :class:`Info` object) corresponding + to the owner of ``path``. If ``uid`` is specified, ``path`` is used + solely to determine the filesystem ``uid`` was determined from. It + will fall back to the local passwd database, and return ``None`` + if no information is available. If ``path`` is omitted, it will + fall back to the current working directory. + + This function implements a plugin interface named + :ref:`wizard.user.passwd`. + """ + if path is None: + path = os.getcwd() + path = os.path.realpath(path) + if not uid: + uid = os.stat(path).st_uid + r = plugin.hook("wizard.user.passwd", [path, uid]) + if r is not None: + return r + try: + return Info.pwentry(pwd.getpwuid(uid)) + except KeyError: + return None + +def pwnam(name): + """ + This user converts a username into a :class:`Info` object using + *only* the local password database. + """ + return Info.pwentry(pwd.getpwnam(name)) + +class Info(object): + """ + Object containing information describing a user. It is analogous to + passwd, but has dropped the password field and dedicated the + ``gecos`` field for real name information. + + .. note:: + + If a platform does not support retrieving information about a + field, it may have the value ``None``. + """ + #: Login name + name = None + #: User ID + uid = None + #: Group ID + gid = None + #: Real name + realname = None + #: Home directory + homedir = None + #: Default command interpreter + shell = None + @staticmethod + def pwentry(pwentry): + return Info(pwentry.pw_name, pwentry.pw_uid, pwentry.pw_gid, + pwentry.pw_gecos.split(",")[0], pwentry.pw_dir, pwentry.pw_shell) + def __init__(self, name, uid, gid, realname, homedir, shell): + self.name = name + self.uid = uid + self.gid = gid + self.realname = realname + self.homedir = homedir + self.shell = shell + self._email = None + @property + def email(self): + """The email of this user, calculated on the fly.""" + if self._email is None: + self._email = email(self.name) + return self._email