summaryrefslogtreecommitdiffstats
path: root/userconfig
diff options
context:
space:
mode:
Diffstat (limited to 'userconfig')
-rwxr-xr-xuserconfig/unixauthdb.py1154
-rw-r--r--userconfig/userconfig.desktop49
-rwxr-xr-xuserconfig/userconfig.py1753
3 files changed, 2956 insertions, 0 deletions
diff --git a/userconfig/unixauthdb.py b/userconfig/unixauthdb.py
new file mode 100755
index 0000000..92ffcc8
--- /dev/null
+++ b/userconfig/unixauthdb.py
@@ -0,0 +1,1154 @@
+#!/usr/bin/python
+###########################################################################
+# Copyright (C) 2004-2006 by Simon Edwards
+# <simon@simonzone.com>
+#
+# Copyright: See COPYING file that comes with this distribution
+#
+###########################################################################
+# An API for querying and modifying the authorisation database on Unix systems.
+#
+# The first function that you need to use is getContext(). It returns a
+# Context object that contains all relevant information concerning
+# the current authorisation database on this machine.
+
+import crypt
+import random
+import fcntl
+import time
+import os
+import os.path
+import stat
+import shutil
+import codecs
+import locale
+import tempfile
+
+ldaperror = ""
+try:
+ import ldap
+except ImportError:
+ ldaperror = "The LDAP Python Module is not installed, but needed to use LDAP. Install it."
+
+def createTempFile(origfile):
+ origstat = os.stat(origfile)
+ tmp_prefix = os.path.basename(origfile) + "."
+ tmp_dir = os.path.dirname(origfile)
+ try:
+ ret = tempfile.mkstemp(prefix=tmp_prefix, dir=tmp_dir)
+ except:
+ raise IOError, "Unable to create a new temporary file for " + origfile
+ (fd, tmpfile) = ret
+ shutil.copymode(origfile, tmpfile)
+ os.chown(tmpfile, origstat.st_uid, origstat.st_gid)
+
+ return ret
+
+def getContext(editmode=False):
+ """Get a Context object describing the system's authorisation database.
+
+ Parameters:
+
+ editmode - Set to true if you also wish change the information in this
+ context. Root access is required. Defaults to false.
+
+ Returns a Context object.
+
+ If the environmental variable "USERCONFIG_USES_LDAP" is set to "true",
+ userconfig will use LDAP as the backend. This feature is in development
+ and using it is not recommended, it won't work.
+ """
+
+ # Detect what kind of auth system we are running on and create
+ # and initialise the corresponding Context object type.
+
+ # Check for Mandrake
+
+ # Check libuser.conf
+ try:
+ if os.environ["USERCONFIG_USES_LDAP"].lower() == "true":
+ use_ldap = True
+ except KeyError,e:
+ use_ldap = False
+ if not use_ldap:
+ return PwdContext(editmode)
+ else:
+ print "==================================================================="
+ print "Warning:"
+ print "\tYou are using LDAP as backend. This feature is under development"
+ print "\tand it is currently not recommended to use it."
+ print "\tIf you do not want to use LDAP as backend, set the environmental"
+ print "\tvariabale 'USERCONFIG_USES_LDAP' to 'False'."
+ print "==================================================================="
+ return LdapContext(editmode)
+
+###########################################################################
+# Base classes.
+#
+class Context(object):
+ """Contains all of the information about the current authorisation
+ database, plus some useful methods for modify this information.
+
+ """
+ def __init__(self):
+ self._users = []
+ self._groups = []
+ self._shells = None
+ self._setDefaultValues()
+
+ def newUser(self,defaults=False,systemuser=False):
+ """Create a new UnixUser object.
+
+ Creates a new blank UnixUser object. The object is not part of the
+ current Context. You need to add it yourself using addUser().
+
+ Newly allocated UIDs are unique with respect to the list of UnixUser
+ objects in the Context.
+
+ Keyword arguments:
+ defaults -- Set to true if the new object should be filled in with
+ reasonable default values for the UID and username.
+ (default False)
+ systemuser -- Should the new user be allocated a UID from the system
+ range of UIDs. (default is False)
+
+ Returns a new UnixUser object.
+ """
+ newuserobj = self._createUser()
+ if defaults:
+ if systemuser:
+ r = xrange(0,self.last_system_uid)
+ else:
+ r = xrange(self.first_uid,self.last_uid)
+ for candiate in r:
+ for u in self._users:
+ if u.getUID()==candiate:
+ break
+ else:
+ newuserobj.setUID(candiate)
+ break
+
+ if self.lookupUsername(u'new_user') is None:
+ newuserobj.setUsername(u'new_user')
+ else:
+ i = 1
+ while 1:
+ if self.lookupUsername(u'new_user_'+str(i)) is None:
+ newuserobj.setUsername(u'new_user_'+str(i))
+ break
+ i += 1
+ return newuserobj
+
+ def getUsers(self):
+ """Get a list of all existing users.
+
+ Returns an array of UnixUser objects.
+ """
+ #print "USERS:", self._users
+ return self._users[:]
+
+ def getGroups(self):
+ """Get a list of all existing groups.
+
+ Returns an array of UnixGroup objects.
+ """
+ try:
+ self._groups.remove("new_user")
+ except ValueError:
+ print "no user removed"
+ pass
+ return self._groups[:]
+
+ def newGroup(self,defaults=False,systemgroup=False):
+ """Create a new UnixGroup object.
+
+ Creates a new blank UnixGroup object. The object is not part of the
+ current Context. You need to add it yourself using addGroup().
+
+ Newly allocated GIDs are unique with respect to the list of UnixGroup
+ objects in the Context.
+
+ Keyword arguments:
+ defaults -- Set to true if the new object should be filled in with
+ reasonable default values for the GID and groupname.
+ (default False)
+ systemgroup -- Set to True if the newly allocated GID should come
+ from the pool of system group IDs. (default False)
+
+ Returns a new UnixGroup object.
+ """
+ newgroupobj = self._createGroup()
+ if defaults:
+ if systemgroup:
+ r = xrange(0,self.last_system_gid)
+ else:
+ r = xrange(self.first_gid,self.last_gid)
+ for candiate in r:
+ for u in self._groups:
+ if u.getGID()==candiate:
+ break
+ else:
+ newgroupobj.setGID(candiate)
+ break
+ if self.lookupGroupname(u'new_group') is None:
+ newgroupobj.setGroupname(u'new_group')
+ else:
+ i = 1
+ while 1:
+ if self.lookupGroupname(u'new_user_'+str(i)) is None:
+ newgroupobj.setGroupname(u'new_user_'+str(i))
+ break
+ i += 1
+ return newgroupobj
+
+ def _createGroup(self):
+ raise NotImplementedError, "Context.newGroup()"
+
+ def addUser(self,userobj):
+ """Adds the given user to the authorisation database.
+
+ This change only takes effect after calling context.save().
+
+ Keyword arguments:
+ userobj -- The UnixUser object to add.
+ """
+ self._users.append(userobj)
+
+ def addGroup(self,groupobj):
+ """Adds the given group to the authorisation database.
+
+ This change only takes effect after calling context.save().
+
+ Keyword arguments:
+ groupobj -- The UnixGroup object to add.
+ """
+ if groupobj not in self._groups:
+ self._groups.append(groupobj)
+
+ def removeUser(self,userobj):
+ """Removes the given user object from the authorisation database.
+
+ The user is also removed from all groups.
+
+ This change only takes effect after calling context.save().
+ """
+ for g in userobj.getGroups():
+ userobj.removeFromGroup(g)
+
+ self._users.remove(userobj)
+
+ def removeGroup(self,groupobj):
+ """Removes the given group object from the authorisation database.
+
+ All users are removed from the group.
+
+ This change only takes effect after calling context.save().
+ """
+ for u in groupobj.getUsers():
+ u.removeFromGroup(groupobj)
+
+ self._groups.remove(groupobj)
+
+ def lookupUID(self,uid):
+ """Lookup a UnixUser object by its numeric user ID.
+
+ Keyword arguments:
+ uid -- User ID to lookup, integer.
+
+ Returns the matching UnixUser object or None if it was not found.
+ """
+ for user in self._users:
+ if user.getUID()==uid:
+ return user
+ return None
+
+ def lookupUsername(self,username):
+ """Lookup a UnixUser object by username.
+
+ Keyword arguments:
+ username -- Username to lookup, string.
+
+ Returns the matching UnixUser object or None if it was not found.
+ """
+ for user in self._users:
+ if user.getUsername()==username:
+ return user
+ return None
+
+ def lookupGID(self,gid):
+ """Lookup a UnixGroup object by its numeric group ID.
+
+ Keyword arguments:
+ gid -- Group ID to lookup, integer.
+
+ Returns the matching UnixGroup object or None if it was not found.
+ """
+ for group in self._groups:
+ if group.getGID()==gid:
+ return group
+ return None
+
+ def lookupGroupname(self,groupname):
+ """Lookup a UnixGroup object by groupname.
+
+ Returns the matching UnixGroup object or None if it was not found.
+ """
+ for group in self._groups:
+ if group.getGroupname()==groupname:
+ return group
+ return None
+
+ def getUserShells(self):
+ """Get the list of available login shells.
+
+ Returns an array of strings.
+ """
+ if self._shells is None:
+ self._shells = []
+ fhandle = codecs.open('/etc/shells','r',locale.getpreferredencoding())
+ for l in fhandle.readlines():
+ # TODO: strangely this lets some comented lines slip through
+ if len(l.strip()) > 1 and l.strip()[0] is not "#":
+ # Only show existing shells
+ if os.path.isfile(l.strip()):
+ self._shells.append(l.strip())
+ fhandle.close()
+ return self._shells[:]
+
+ def save(self):
+ """Synchronises the Context with the underlying operating system.
+
+ After a successful save, any changes to the Context will be reflected
+ system wide.
+ """
+ raise NotImplementedError, "Context.save()"
+
+ def createHomeDirectory(self,userobj):
+ if os.path.exists(userobj.getHomeDirectory()):
+ raise IOError, u"Home directory %s already exists." % userobj.getHomeDirectory()
+
+ # Copy the skeleton directory over
+ shutil.copytree(self._getSkeletonDirectory(),userobj.getHomeDirectory(),True)
+
+ # Fix the file ownership stuff
+ uid = userobj.getUID()
+ gid = userobj.getPrimaryGroup().getGID()
+ os.chmod(userobj.getHomeDirectory(),self.dir_mode)
+ #os.system("chmod "+self.dir_mode+" "+userobj.getHomeDirectory())
+ #print "Setting permissions:", userobj.getHomeDirectory(),self.dir_mode
+ os.lchown(userobj.getHomeDirectory(),uid,gid)
+ for root,dirs,files in os.walk(userobj.getHomeDirectory()):
+ for d in dirs:
+ os.lchown(os.path.join(root,d),uid,gid)
+ for f in files:
+ os.lchown(os.path.join(root,f),uid,gid)
+
+ def removeHomeDirectory(self,userobj):
+ if os.path.exists(userobj.getHomeDirectory()):
+ shutil.rmtree(userobj.getHomeDirectory())
+
+ def _createUser(self):
+ raise NotImplementedError, "Context._createUser()"
+
+ def _sanityCheck(self):
+ userids = []
+ for u in self._users:
+ if isinstance(u,UnixUser)==False:
+ raise TypeError,"Found an object in the list of users that is not a UnixUser object."
+ uid = u.getUID()
+ if uid in userids:
+ raise ValueError, "User ID %i appears more than once." % uid
+ userids.append(uid)
+ u._sanityCheck()
+
+ groupids = []
+ for g in self._groups:
+ if isinstance(g,UnixGroup)==False:
+ raise TypeError,"Found an object in the list of groups that is not a UnixGroup object."
+ gid = g.getGID()
+ if gid in groupids:
+ raise ValueError, "Group ID %i appears more than once." % gid
+ groupids.append(gid)
+ g._sanityCheck()
+
+ def _getSkeletonDirectory(self):
+ return self.skel
+
+ def _readAdduserConf(self):
+ """ Fill a dictionary with the values from /etc/adduser.conf
+ which then can be used as default values, if the file exists
+ at least.
+ Attention: We're not validating!"""
+ self.defaults = {}
+ self.adduserconf = '/etc/adduser.conf'
+ if not os.path.isfile(self.adduserconf):
+ return
+ fhandle = codecs.open(self.adduserconf,'r',locale.getpreferredencoding())
+ for line in fhandle.readlines():
+ line = line.strip()
+ parts = line.split("=")
+ if len(parts) == 2:
+ self.defaults[str(parts[0].strip())] = parts[1].strip()
+
+ def _setDefaultValues(self):
+ """ Set a lot of default values for UIDs and GIDs, try to use the values
+ from /etc/adduser.conf."""
+ self._readAdduserConf()
+
+ try:
+ self.skel = self.defaults["SKEL"]
+ except KeyError:
+ self.skel = '/etc/skel'
+
+ # IDs for new users and groups.
+ try:
+ self.first_uid = int(self.defaults['FIRST_UID'])
+ except (KeyError,ValueError):
+ self.first_uid = 1000
+
+ try:
+ self.last_uid = int(self.defaults["LAST_UID"])
+ except (KeyError,ValueError):
+ self.last_uid = 29999
+
+ try:
+ self.first_gid = int(self.defaults["FIRST_GID"])
+ except (KeyError,ValueError):
+ self.first_gid = 1000
+
+ try:
+ self.last_gid = int(self.defaults["LAST_GID"])
+ except (KeyError,ValueError):
+ self.last_gid = 65534
+
+ # Which IDs are system user and system groups?
+ try:
+ self.first_system_uid = int(self.defaults["FIRST_SYSTEM_UID"])
+ except (KeyError,ValueError):
+ self.first_system_uid = 500
+
+ try:
+ self.last_system_uid = int(self.defaults["LAST_SYSTEM_UID"])
+ except (KeyError,ValueError):
+ self.last_system_uid = 65534
+
+ try:
+ self.first_system_gid = int(self.defaults["FIRST_SYSTEM_GID"])
+ except (KeyError,ValueError):
+ self.first_system_gid = 500
+
+ try:
+ self.last_system_gid = int(self.defaults["LAST_SYSTEM_GID"])
+ except (KeyError,ValueError):
+ self.last_system_gid = 65534
+
+ # More defaults which might make sense.
+ try:
+ self.dir_mode = int(self.defaults["DIR_MODE"],8)
+ except (KeyError,ValueError):
+ self.dir_mode = int("0755",8)
+ print "Didn't read default DIR_MODE"
+
+ try:
+ self.dhome = self.defaults["DHOME"]
+ except KeyError:
+ self.dhome = "/home"
+
+ try:
+ self.dshell = self.defaults["DSHELL"]
+ except KeyError:
+ # Will be set in showNewUser()
+ self.dshell = None
+
+###########################################################################
+class UnixUser(object):
+ def __init__(self,context):
+ self._context = context
+ self._uid = None
+ self._username = None
+
+ # UnixGroup object.
+ self._primarygroup = None
+
+ # List of UnixGroup objects.
+ self._groups = []
+
+ self._gecos = None
+ self._homedirectory = None
+ self._loginshell = None
+
+ self._islocked = False
+
+ self._encpass = ""
+
+ # FIXME : This should actually be days since epoch or something like this
+ self._passlastchange = 0
+ self._passminimumagebeforechange = 0
+ self._passmaximumage = None
+ self._passexpirewarn = 7
+ self._passexpiredisabledays = None
+ self._disableddays = None
+
+ def polish(self):
+ primary_group = self._context.lookupGID(self._gid)
+ if primary_group is None:
+ # The GID didn't match an existing group. Quickly make a new group.
+ new_group = self._context.newGroup()
+ new_group.setGID(self._gid)
+
+ new_group_name = u"group%i" % self._gid
+ i = 0
+ while self._context.lookupGroupname(new_group_name) is not None:
+ i += 1
+ new_group_name = u"group%i_%i" % (self._gid,i)
+ new_group.setGroupname(new_group_name)
+
+ self._context.addGroup(new_group)
+ primary_group = new_group
+
+ self.setPrimaryGroup(primary_group)
+ for group in self._context._groups:
+ if group.contains(self):
+ self._groups.append(group)
+
+ def getUID(self):
+ """Get the unix user ID.
+
+ Returns the integer.
+ """
+ return self._uid
+
+ def setUID(self,uid):
+ """Set the unix user ID.
+
+ Keyword arguments:
+ uid -- Integer user id.
+ """
+ uid = int(uid)
+ if uid<0:
+ raise ValueError, "User ID (%i) is a negative number." % uid
+ self._uid = uid
+
+ def isSystemUser(self):
+ """See if this user is a system user.
+
+ Returns True or False.
+ """
+ return not (self._context.first_uid <= self._uid < self._context.last_uid)
+
+ def getUsername(self): return self._username
+
+ def setUsername(self,username): self._username = username
+
+ def getPrimaryGroup(self):
+ """Get the primary group for this user.
+
+ Returns a UnixGroup object.
+ """
+ return self._primarygroup
+
+ def setPrimaryGroup(self,groupobj):
+ """Set the primary group for this user.
+
+ If the given group is not part of this user's list of groups, then
+ it will be added.
+
+ Keyword arguments:
+ groupobj -- The group to set as the primary group.
+ """
+ self.addToGroup(groupobj)
+ self._primarygroup = groupobj
+
+ def getGroups(self):
+ """Get the list of groups that this user belongs to.
+
+ The user's primary group is also included in the returned list.
+
+ Returns a list of UnixGroup objects. Modify the list does not affect
+ this UnixUser object.
+ """
+ return self._groups[:]
+
+ def addToGroup(self,groupobj):
+ """Add this user to the given group.
+
+ Keyword arguments:
+ groupobj -- UnixGroup object.
+ """
+ groupobj._addUser(self)
+ if groupobj not in self._groups:
+ self._groups.append(groupobj)
+
+ def removeFromGroup(self,groupobj):
+ """Remove this user from the given group.
+
+ If group is current this user's primary group, then
+
+ Keyword arguments:
+ groupobj -- UnixGroup object.
+ """
+ groupobj._removeUser(self)
+ try:
+ self._groups.remove(groupobj)
+ except ValueError:
+ pass
+ if self._primarygroup is groupobj:
+ if len(self._groups)==0:
+ self._primarygroup = None
+ else:
+ self._primarygroup = self._groups[0]
+
+ def setPassword(self,password):
+ # Make some salt.
+ space = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQSRTUVWXYZ0123456789./'
+ salt = ""
+ for x in range(8):
+ salt += space[random.randint(0,len(space)-1)]
+ self._encpass = crypt.crypt(password,'$1$'+salt+'$')
+
+ def isLocked(self): return self._islocked
+ def setLocked(self,locked): self._islocked = locked
+
+ def getRealName(self):
+ if not self._gecos:
+ return ""
+ try:
+ return self._gecos.split(",")[0]
+ except AttributeError:
+ return self._gecos
+
+ def setRealName(self,realname): self._gecos = realname
+ def getHomeDirectory(self): return self._homedirectory
+ def setHomeDirectory(self,homedirectory): self._homedirectory = homedirectory
+ def getLoginShell(self): return self._loginshell
+ def setLoginShell(self,loginshell): self._loginshell = loginshell
+
+ # 'None' means that there is no maximum password age.
+ def getMaximumPasswordAge(self): return self._passmaximumage
+ def setMaximumPasswordAge(self,days): self._passmaximumage = days
+
+ def getMinimumPasswordAgeBeforeChange(self): return self._passminimumagebeforechange
+ def setMinimumPasswordAgeBeforeChange(self,days): self._passminimumagebeforechange = days
+ def getPasswordDisableAfterExpire(self): return self._passexpiredisabledays
+ def setPasswordDisableAfterExpire(self,days): self._passexpiredisabledays = days
+ def getPasswordExpireWarning(self): return self._passexpirewarn
+ def setPasswordExpireWarning(self,days): self._passexpirewarn = days
+ def getLastPasswordChange(self): return self._passlastchange
+ def getExpirationDate(self): return self._disableddays
+ def setExpirationDate(self,unixdate): self._disableddays = unixdate
+
+ def __str__(self):
+ return "%s(%i)" % (self._username,self._uid)
+
+ def _sanityCheck(self):
+ if self._primarygroup is None:
+ raise ValueError,"Userobj has no primary group!"
+ if self._uid is None:
+ raise ValueError,"Userobj has no UID!"
+
+###########################################################################
+class UnixGroup(object):
+ def __init__(self,context):
+ self._context = context
+
+ # List of UnixUser objects.
+ self._members = []
+
+ self._gid = None
+ self._groupname = None
+
+ def contains(self,userobj):
+ """Check if a the given user is a member of this group.
+
+ Returns True or False.
+ """
+ return userobj in self._members
+
+ def polish(self): pass
+ def isSystemGroup(self):
+ """Check if this group is a system group.
+
+ Returns True or False.
+ """
+ return not (self._context.first_gid <= self._gid < self._context.last_gid)
+ #return not (500 <= self._gid < 65534)
+
+ def getGID(self):
+ """Get the unix group ID.
+
+ Returns the integer group id.
+ """
+ return self._gid
+
+ def setGID(self,gid):
+ """Set the unix group ID.
+
+ Keyword arguments:
+ gid -- new group id, integer.
+ """
+ self._gid = gid
+
+ def getGroupname(self): return self._groupname
+ def setGroupname(self,groupname): self._groupname = groupname
+ def getUsers(self): return self._members[:]
+ def _addUser(self,userobj):
+ if not self.contains(userobj):
+ self._members.append(userobj)
+
+ def _removeUser(self,userobj):
+ try:
+ self._members.remove(userobj)
+ except ValueError:
+ pass
+
+ def __str__(self):
+ # FIXME encoding
+ return str(self._groupname) + " (" + str(self._gid) + ") " + str([str(u) for u in self._members])
+
+ def _sanityCheck(self):
+ pass
+
+###########################################################################
+class PwdContext(Context):
+ #def __init__(self,editmode,passwordfile="etc-passwd",groupfile='etc-group',shadowfile="etc-shadow"):
+ def __init__(self,editmode,passwordfile="/etc/passwd",groupfile='/etc/group',shadowfile="/etc/shadow"):
+ Context.__init__(self)
+ self.__editmode = editmode
+ self.__passwordfile = passwordfile
+ self.__groupfile = groupfile
+ self.__shadowfile = shadowfile
+ self._setDefaultValues()
+
+ # Read in the password file
+ fhandle = codecs.open(passwordfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+passwordfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ newuserobj = self.newUser(False)
+ newuserobj._initString(line)
+ self._users.append(newuserobj)
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ # Read the group file
+ fhandle = codecs.open(groupfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+groupfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ newgroupobj = self.newGroup(False)
+ newgroupobj._initString(line)
+ self._groups.append(newgroupobj)
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ if self.__editmode:
+ # Load up the info from the shadow file too.
+ fhandle = codecs.open(shadowfile,'r',locale.getpreferredencoding())
+ if LockFDRead(fhandle.fileno())==False:
+ raise IOError,"Unable to lock the "+shadowfile+" file."
+ try:
+ for line in fhandle.readlines():
+ if line.strip()!="":
+ try:
+ (username,encpass,passlastchange,passminimumagebeforechange,passmaximumage, \
+ passexpirewarn,passexpiredisabledays,disableddays,reserve) = \
+ tuple(line.strip().split(":"))
+ userobj = self.lookupUsername(username)
+ if userobj is not None:
+ if encpass=="":
+ encpass = u"*"
+ userobj._encpass = encpass
+ if userobj._encpass[0]=='!':
+ userobj._islocked = True
+ userobj._encpass = userobj._encpass[1:]
+ else:
+ userobj._islocked = False
+ # FIXME : set time
+ if passlastchange and passlastchange!=u"None":
+ userobj._passlastchange = int(passlastchange)
+ else:
+ passlastchange = 0
+
+ if passminimumagebeforechange=="":
+ passminimumagebeforechange = None
+ else:
+ passminimumagebeforechange = int(passminimumagebeforechange)
+ if passminimumagebeforechange>=99999:
+ passminimumagebeforechange = None
+ userobj._passminimumagebeforechange = passminimumagebeforechange
+
+ if passmaximumage=="":
+ passmaximumage = None
+ else:
+ passmaximumage = int(passmaximumage)
+ if passmaximumage>=99999:
+ passmaximumage = None
+ userobj._passmaximumage = passmaximumage
+
+ if passexpirewarn=="":
+ passexpirewarn = None
+ else:
+ passexpirewarn = int(passexpirewarn)
+ if passexpirewarn>=99999:
+ passexpirewarn = None
+ userobj._passexpirewarn = passexpirewarn
+
+ if passexpiredisabledays=="":
+ userobj._passexpiredisabledays = None
+ else:
+ userobj._passexpiredisabledays = int(passexpiredisabledays)
+
+ if disableddays=="" or disableddays==u"99999":
+ userobj._disableddays = None
+ else:
+ userobj._disableddays = int(disableddays)
+
+ userobj._reserve = reserve
+ else:
+ print "Couldn't find",username
+ except ValueError:
+ pass
+ finally:
+ UnlockFD(fhandle.fileno())
+ fhandle.close()
+
+ for group in self._groups:
+ group.polish()
+ for user in self._users:
+ user.polish()
+
+ def _createUser(self):
+ return PwdUser(self)
+
+ def _createGroup(self):
+ return PwdGroup(self)
+
+ def save(self):
+ if self.__editmode==False:
+ raise IOError, "Can't save, the context was created Read only."
+
+ self._sanityCheck()
+
+ # Write out the new password file.
+ (fd, tmpname) = createTempFile(self.__passwordfile)
+ for u in self._users:
+ os.write(fd, u._getPasswdEntry().encode(locale.getpreferredencoding(),'replace'))
+ #print u._getPasswdEntry()
+ os.close(fd)
+
+ # Update the passwd file
+ passwordlock = os.open(self.__passwordfile, os.O_WRONLY) # FIXME encoding
+ if LockFDWrite(passwordlock)==False:
+ raise IOError,"Couldn't get a write lock on "+self.__passwordfile
+ try:
+ os.rename(tmpname, self.__passwordfile)
+ finally:
+ UnlockFD(passwordlock)
+ os.close(passwordlock)
+
+ # Write out the new group file
+ (fd, tmpname) = createTempFile(self.__groupfile)
+ origstat = os.stat(self.__groupfile)
+ for g in self._groups:
+ os.write(fd,g._getGroupFileEntry().encode(locale.getpreferredencoding()))
+ #print g._getGroupFileEntry()[:-1]
+ os.close(fd)
+ os.chown(tmpname, origstat.st_uid, origstat.st_gid)
+
+ # Update the group file.
+ grouplock = os.open(self.__groupfile, os.O_WRONLY)
+ if LockFDWrite(grouplock)==False:
+ raise IOError,"Couldn't get write lock on "+self.__groupfile
+ try:
+ os.rename(tmpname, self.__groupfile)
+ finally:
+ UnlockFD(grouplock)
+ os.close(grouplock)
+
+ # Write out the new shadow file
+ origstat = os.stat(self.__shadowfile)
+ (fd, tmpname) = createTempFile(self.__shadowfile)
+ for u in self._users:
+ os.write(fd,u._getShadowEntry().encode(locale.getpreferredencoding()))
+ #print u._getShadowEntry()[:-1]
+ os.close(fd)
+
+ # Update the shadow file.
+
+ # Make sure that it is writable.
+ if (origstat.st_mode & stat.S_IWUSR)==0:
+ os.chmod(self.__shadowfile,origstat.st_mode|stat.S_IWUSR)
+
+ shadowlock = os.open(self.__shadowfile, os.O_WRONLY)
+ if LockFDWrite(shadowlock)==False:
+ raise IOError,"Couldn't get write lock on "+self.__shadowfile
+ try:
+ os.rename(tmpname, self.__shadowfile)
+ finally:
+ UnlockFD(shadowlock)
+ os.close(shadowlock)
+
+ # set the permissions back to thier default.
+ if (origstat.st_mode & stat.S_IWUSR)==0:
+ os.chmod(self.__shadowfile,origstat.st_mode)
+
+###########################################################################
+class LdapContext(Context):
+
+ def __init__(self,editmode,server="localhost",admin_dn="",admin_pass=""):
+ """ Connect to the LDAP server and invoke further actions.
+ """
+ Context.__init__(self)
+ # admin_dn is DistinguishedName? (or dn, for short)
+ self.server = server
+ self.baseDN = "dc=vizZzion,dc=net"
+
+ self.url = "ldap://"+self.server
+
+ self.ldapserver = ldap.initialize(self.url)
+ self.ldapserver.protocol_version = ldap.VERSION3
+
+ self.editmode = editmode
+ if not self.editmode:
+ self.ldapserver.simple_bind("admin",admin_pass)
+ print "Connected to ", self.url
+
+ self._users = self._getUsers()
+
+ def _getUsers(self):
+ """ Retrieve a list of users from the LDAP server.
+ """
+ _users = []
+ print "LdapContext._getUsers"
+ searchScope = ldap.SCOPE_SUBTREE
+ retrieveAttributes = None
+ searchFilter = "cn=*"
+ try:
+ ldap_result_id = self.ldapserver.search(self.baseDN, searchScope, searchFilter, retrieveAttributes)
+ result_set = []
+ while 1:
+ result_type, result_data = self.ldapserver.result(ldap_result_id, 0)
+ if (result_data == []):
+ break
+ else:
+ if result_type == ldap.RES_SEARCH_ENTRY:
+ #print result_data[0][1]
+ #print " --------------------- "
+ result_set.append(result_data[0][1])
+ #print result_set
+ except ldap.LDAPError, e:
+ print "ERROR: ",e
+
+ if len(result_set) == 0:
+ print "No Results."
+ return
+ count = 0
+ """
+ for entry in result_set:
+ for d in entry.keys():
+ print d, "::", entry[d]
+ print "======== Next User =============="
+ """
+ # Walk through result_set and create users.
+ for entry in result_set:
+ try:
+ name = entry['cn'][0]
+ login = entry['uid'][0]
+ loginshell = entry['loginShell'][0]
+ homedirectory = entry['homeDirectory'][0]
+ uid = entry['uidNumber'][0]
+ gid = entry['gidNumber'][0]
+ count = count + 1
+ #print "\n%d. User: %s\n\tName: %s\n\tShell: %s\n\tHomeDir: %s\n\tUID: %s\n\tGID: %s\n" %\
+ # (count, login, name, loginshell, homedirectory, uid, gid)
+ # Create a new userobject
+ new_user = self._createUser()
+ new_user.setHomeDirectory(homedirectory)
+ new_user.setUID(uid)
+ new_user.setRealName(name)
+ new_user.setLoginShell(loginshell)
+ new_user.setUsername(login)
+ _users.append(new_user)
+ print "Number of Users:", len(self._users)
+
+ except KeyError, e:
+ # Debugging output...
+ print "ERR:: ",e
+ print 'err:: ',entry
+ return _users
+
+ def _createUser(self):
+ return LdapUser(self)
+
+ def _createGroup(self):
+ return LdapGroup(self)
+
+ def save(self):
+ print "LdapContext.save() does nothing yet."
+
+###########################################################################
+class LdapUser(UnixUser):
+
+ def __str__(self):
+ return "LdapUser: %s(%i)" % (self._username,self._uid)
+
+
+###########################################################################
+class LdapGroup(UnixGroup):
+
+ def __str__(self):
+ return "LdapGroup: %s(%i)" % (self._username,self._uid)
+
+
+###########################################################################
+class PwdUser(UnixUser):
+ def __init__(self,context):
+ UnixUser.__init__(self,context)
+ self._reserve = u""
+
+ def _initString(self,line):
+ (self._username,x,self._uid,self._gid,self._gecos,self._homedirectory, \
+ self._loginshell) = tuple(line.strip().split(":"))
+ self._uid = int(self._uid)
+ self._gid = int(self._gid)
+
+ def _getPasswdEntry(self):
+ return u":".join( [self._username,
+ u"x",
+ unicode(self._uid),
+ unicode(self._primarygroup.getGID()),
+ self._gecos,
+ self._homedirectory,
+ self._loginshell ] ) + u"\n"
+
+ def _getShadowEntry(self):
+ if self._islocked:
+ encpass = u'!' + self._encpass
+ else:
+ encpass = self._encpass
+
+ if self._passminimumagebeforechange==None:
+ passminimumagebeforechange = ""
+ else:
+ passminimumagebeforechange = str(self._passminimumagebeforechange)
+
+ if self._passmaximumage==None:
+ passmaximumage = u"99999"
+ else:
+ passmaximumage = unicode(self._passmaximumage)
+
+ if self._disableddays==None:
+ disableddays = u""
+ else:
+ disableddays = unicode(self._disableddays)
+
+ if self._passexpiredisabledays==None:
+ passexpiredisabledays = u""
+ else:
+ passexpiredisabledays = unicode(self._passexpiredisabledays)
+
+ if self._passexpirewarn==None:
+ passexpirewarn = u""
+ else:
+ passexpirewarn = unicode(self._passexpirewarn)
+
+ return u":".join( [self._username,
+ encpass,
+ unicode(self._passlastchange),
+ passminimumagebeforechange,
+ passmaximumage,
+ passexpirewarn,
+ passexpiredisabledays,
+ disableddays,
+ self._reserve ])+ u"\n"
+
+###########################################################################
+class PwdGroup(UnixGroup):
+ def __init__(self,context):
+ UnixGroup.__init__(self,context)
+ self._memberids = u""
+ self._encpass = u""
+
+ def _initString(self,line):
+ (self._groupname,self._encpass,self._gid,self._memberids) = tuple(line.strip().split(":"))
+ self._gid = int(self._gid)
+
+ def polish(self):
+ membernames = self._memberids.split(",")
+ for username in membernames:
+ userobj = self._context.lookupUsername(username)
+ if userobj!=None:
+ self._members.append(userobj)
+
+ def _getGroupFileEntry(self):
+ return u":".join( [ self._groupname,
+ self._encpass,
+ unicode(self._gid),
+ u",".join([u.getUsername() for u in self._members if u.getPrimaryGroup() is not self])]) + u"\n"
+
+###########################################################################
+def LockFDRead(fd):
+ retries = 6
+ while retries!=0:
+ try:
+ fcntl.lockf(fd,fcntl.LOCK_SH | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ # Wait a moment
+ time.sleep(1)
+ return False
+
+def LockFDWrite(fd):
+ retries = 6
+ while retries!=0:
+ try:
+ fcntl.lockf(fd,fcntl.LOCK_EX | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ # Wait a moment
+ time.sleep(1)
+ return False
+
+def UnlockFD(fd):
+ fcntl.lockf(fd,fcntl.LOCK_UN)
+
+###########################################################################
+
+if __name__=='__main__':
+ print "Testing"
+ context = getContext(True)
+
+ print "Stopping here..."
+ #import sys
+ #sys.exit(0) ## Remove.
+ #print "Users:"
+ #for user in context.getUsers():
+ for user in context._users:
+ print "--------------------------------------------------"
+ print "UID:",user.getUID()
+ print "Is system user:",user.isSystemUser()
+ print "Username:",user.getUsername()
+ print "Primary Group:",str(user.getPrimaryGroup())
+ print "Groups:",[str(u) for u in user.getGroups()]
+ print "Is locked:",user.isLocked()
+ print "Real name:",user.getRealName()
+ print "Home Dir:",user.getHomeDirectory()
+ print "Maximum password age:",user.getMaximumPasswordAge()
+ print "Minimum password age before change:",user.getMinimumPasswordAgeBeforeChange()
+ print "Expire warning:",user.getPasswordExpireWarning()
+ print "Disable after Expire:",user.getPasswordDisableAfterExpire()
+ #print user._getPasswdEntry()
+
+ print "Groups"
+ for group in context.getGroups():
+ print str(group)
+ #print group._getGroupFileEntry()
+
+ print "Saving"
+ context.save()
diff --git a/userconfig/userconfig.desktop b/userconfig/userconfig.desktop
new file mode 100644
index 0000000..276bfc3
--- /dev/null
+++ b/userconfig/userconfig.desktop
@@ -0,0 +1,49 @@
+[Desktop Entry]
+Name=User Management
+Name[el]=Διαχείριση χρηστών
+Name[es]=Administrador de usuarios
+Name[et]=Kasutajate haldamine
+Name[it]=Gestione degli utenti
+Name[ja]=ユーザの管理
+Name[nl]=Gebruikersbeheer
+Name[pt]=Gestão de Utilizadores
+Name[pt_BR]=Gerenciamento de Usuários
+Name[sr]=Управљање корисницима
+Name[sr@Latn]=Upravljanje korisnicima
+Name[sv]=Användarhantering
+Name[xx]=xxUser Managementxx
+Comment=Users & Groups Administration
+Comment[el]=Διαχείριση χρηστών & ομάδων
+Comment[es]=Administrador de usuarios y grupos
+Comment[et]=Kasutajate ja gruppide haldamine
+Comment[it]=Amministrazione di utenti e gruppi
+Comment[ja]=ユーザとグループの管理
+Comment[nl]=Gebruikers en groepen beheren
+Comment[pt]=Administração dos Utilizadores & Grupos
+Comment[pt_BR]=Administração de Usuários & Grupos
+Comment[sr]=Администрирање корисника и група
+Comment[sr@Latn]=Administriranje korisnika i grupa
+Comment[sv]=Administration av användare och grupper
+Comment[xx]=xxUsers & Groups Administrationxx
+Icon=userconfig.png
+Encoding=UTF-8
+X-KDE-ModuleType=Library
+X-KDE-Library=userconfig
+X-KDE-FactoryName=userconfig
+X-KDE-RootOnly=true
+Type=Application
+Exec=kcmshell System/userconfig
+Categories=Qt;KDE;X-KDE-settings-system;
+GenericName=User Account Editor
+GenericName[el]=Επεξεργαστής λογαριασμών χρηστών
+GenericName[es]=Editor de cuenta de usuario
+GenericName[et]=Kasutajakonto redaktor
+GenericName[it]=Editor degli account degli utenti
+GenericName[ja]=ユーザアカウントエディタ
+GenericName[nl]=Gebruikeraccounts bewerken
+GenericName[pt]=Editor de Contas dos Utilizadores
+GenericName[pt_BR]=Editor de Contas de Usuários
+GenericName[sr]=Уређивач корисничких налога
+GenericName[sr@Latn]=Uređivač korisničkih naloga
+GenericName[sv]=Editor för användarkonton
+GenericName[xx]=xxUser Account Editorxx
diff --git a/userconfig/userconfig.py b/userconfig/userconfig.py
new file mode 100755
index 0000000..b400531
--- /dev/null
+++ b/userconfig/userconfig.py
@@ -0,0 +1,1753 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+###########################################################################
+# userconfig.py - description #
+# ------------------------------ #
+# begin : Wed Apr 30 2003 #
+# copyright : (C) 2003-2006 by Simon Edwards #
+# email : simon@simonzone.com #
+# #
+###########################################################################
+# #
+# 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. #
+# #
+###########################################################################
+
+from qt import *
+from kdeui import *
+from kdecore import *
+from kfile import *
+import sys
+import os.path
+import shutil
+import unixauthdb
+import locale
+
+programname = "userconfig"
+version = "0.8.0"
+# Are we running as a separate standalone application or in KControl?
+standalone = __name__=='__main__'
+
+# Running as the root user or not?
+isroot = os.getuid()==0
+#isroot = True
+
+###########################################################################
+def SptimeToQDate(sptime):
+ t = QDateTime()
+ t.setTime_t(0)
+ return t.addDays(sptime).date()
+
+###########################################################################
+def QDateToSptime(qdate):
+ x = QDateTime()
+ x.setTime_t(0)
+ return x.daysTo(QDateTime(qdate))
+
+###########################################################################
+# Try translating this code to C++. I dare ya!
+if standalone:
+ programbase = KDialogBase
+else:
+ programbase = KCModule
+
+class UserConfigApp(programbase):
+ def __init__(self,parent=None,name=None):
+ global standalone,isroot
+ KGlobal.locale().insertCatalogue("guidance")
+
+ if standalone:
+ KDialogBase.__init__(self,KJanusWidget.Tabbed,i18n("User Accounts and Groups"),
+ KDialogBase.User1|KDialogBase.Close, KDialogBase.Close)
+ self.setButtonText(KDialogBase.User1,i18n("About"))
+ else:
+ KCModule.__init__(self,parent,name)
+ self.setButtons(0)
+ self.aboutdata = MakeAboutData()
+
+ toplayout = QVBoxLayout( self, 0, KDialog.spacingHint() )
+ tabcontrol = QTabWidget(self)
+ toplayout.addWidget(tabcontrol)
+ toplayout.setStretchFactor(tabcontrol,1)
+
+ # Create a configuration object.
+ self.config = KConfig("userconfigrc")
+
+ KGlobal.iconLoader().addAppDir("guidance")
+
+ self.usersToListItems = None
+ self.selecteduserid = None
+ self.selectedgroupid = None
+ self.showsystemaccounts = False
+ self.showsystemgroups = False
+
+ self.updatingGUI = True
+
+
+ self.aboutus = KAboutApplication(self)
+
+ # --- User Tab ---
+ if standalone:
+ usershbox = self.addHBoxPage("Users")
+ vbox = QVBox(usershbox)
+ else:
+ vbox = QVBox(tabcontrol)
+ vbox.setMargin(KDialog.marginHint())
+
+ vbox.setSpacing(KDialog.spacingHint())
+
+ hb = QHBox(vbox)
+ hb.setSpacing(KDialog.spacingHint())
+ vbox.setStretchFactor(hb,0)
+
+ label = QLabel(hb)
+ label.setPixmap(UserIcon("hi32-user"))
+ hb.setStretchFactor(label,0)
+
+ label = QLabel(i18n("User Accounts:"),hb)
+ hb.setStretchFactor(label,1)
+
+ self.userlist = KListView(vbox)
+ self.userlist.addColumn(i18n("Login Name"))
+ self.userlist.addColumn(i18n("Real Name"))
+ self.userlist.addColumn(i18n("UID"))
+ self.userlist.setAllColumnsShowFocus(True)
+ self.userlist.setSelectionMode(QListView.Single)
+
+ self.connect(self.userlist, SIGNAL("selectionChanged(QListViewItem *)"), self.slotListClicked)
+ if isroot:
+ self.connect(self.userlist, SIGNAL("doubleClicked(QListViewItem *)"), self.slotModifyClicked)
+ self.connect(self.userlist, SIGNAL("contextMenu(KListView*,QListViewItem*,const QPoint&)"), self.slotUserContext)
+
+ self.showspecialcheckbox = QCheckBox(i18n("Show system accounts"),vbox)
+ vbox.setStretchFactor(self.showspecialcheckbox,0)
+ self.connect(self.showspecialcheckbox,SIGNAL("toggled(bool)"), self.slotShowSystemToggled)
+
+ hbox = QHBox(vbox)
+ hbox.setSpacing(KDialog.spacingHint())
+
+ vbox.setStretchFactor(hbox,0)
+
+ self.modifybutton = KPushButton(i18n("Modify..."),hbox)
+ hbox.setStretchFactor(self.modifybutton,1)
+ self.connect(self.modifybutton,SIGNAL("clicked()"),self.slotModifyClicked)
+
+ self.newbutton = KPushButton(i18n("New..."),hbox)
+ hbox.setStretchFactor(self.newbutton,1)
+ self.connect(self.newbutton,SIGNAL("clicked()"),self.slotNewClicked)
+
+ self.deletebutton = KPushButton(i18n("Delete..."),hbox)
+ hbox.setStretchFactor(self.deletebutton,1)
+ self.connect(self.deletebutton,SIGNAL("clicked()"),self.slotDeleteClicked)
+
+ detailsbox = QVGroupBox(i18n("Details"),vbox)
+ userinfovbox = QWidget(detailsbox)
+
+ infogrid = QGridLayout(userinfovbox,3,4)
+ infogrid.setSpacing(KDialog.spacingHint())
+
+ label = QLabel(i18n("Login Name:"),userinfovbox)
+ infogrid.addWidget(label,0,0)
+ self.loginnamelabel = KLineEdit("",userinfovbox)
+ self.loginnamelabel.setReadOnly(True)
+ infogrid.addWidget(self.loginnamelabel,0,1)
+
+ label = QLabel(i18n("Real Name:"),userinfovbox)
+ infogrid.addWidget(label,0,2)
+ self.realnamelabel = KLineEdit("",userinfovbox)
+ self.realnamelabel.setReadOnly(True)
+ infogrid.addWidget(self.realnamelabel,0,3)
+
+ label = QLabel(i18n("UID:"),userinfovbox)
+ infogrid.addWidget(label,1,0)
+ self.uidlabel = KLineEdit("",userinfovbox)
+ self.uidlabel.setReadOnly(True)
+ infogrid.addWidget(self.uidlabel,1,1)
+
+ label = QLabel(i18n("Status:"),userinfovbox)
+ infogrid.addWidget(label,1,2)
+ self.statuslabel = KLineEdit("",userinfovbox)
+ self.statuslabel.setReadOnly(True)
+ infogrid.addWidget(self.statuslabel,1,3)
+
+ label = QLabel(i18n("Primary Group:"),userinfovbox)
+ infogrid.addWidget(label,2,0)
+ self.primarygrouplabel = KLineEdit("",userinfovbox)
+ self.primarygrouplabel.setReadOnly(True)
+ infogrid.addWidget(self.primarygrouplabel,2,1)
+
+ label = QLabel(i18n("Secondary Groups:"),userinfovbox)
+ infogrid.addWidget(label,2,2)
+ self.secondarygrouplabel = KLineEdit("",userinfovbox)
+ self.secondarygrouplabel.setReadOnly(True)
+ infogrid.addWidget(self.secondarygrouplabel,2,3)
+
+ if not standalone:
+ tabcontrol.addTab(vbox,i18n("Users"))
+
+ #--- Groups Tab ---
+ if standalone:
+ groupsvbox = self.addVBoxPage(i18n("Groups"))
+ hb = QHBox(groupsvbox)
+ else:
+ groupsvbox = QVBox(tabcontrol)
+ groupsvbox.setMargin(KDialog.marginHint())
+ hb = QHBox(groupsvbox)
+
+ topframe = QFrame(groupsvbox)
+ groupsvbox.setSpacing(KDialog.spacingHint())
+ hb.setSpacing(KDialog.spacingHint())
+ groupsvbox.setStretchFactor(hb,0)
+
+ label = QLabel(hb)
+ label.setPixmap(UserIcon("hi32-group"))
+ hb.setStretchFactor(label,0)
+
+ label = QLabel(i18n("Groups:"),hb)
+ hb.setStretchFactor(label,1)
+
+ groupsplitter = QSplitter(Qt.Vertical,groupsvbox)
+
+ self.grouplist = KListView(groupsplitter)
+ self.grouplist.addColumn(i18n("Group Name"))
+ self.grouplist.addColumn(i18n("GID"))
+ self.grouplist.setAllColumnsShowFocus(True)
+ self.connect(self.grouplist, SIGNAL("selectionChanged(QListViewItem *)"), self.slotGroupListClicked)
+
+ if isroot:
+ self.connect(self.grouplist, SIGNAL("doubleClicked(QListViewItem *)"), self.slotModifyGroupClicked)
+ self.connect(self.grouplist, SIGNAL("contextMenu(KListView*,QListViewItem*,const QPoint&)"),
+ self.slotGroupContext)
+
+ groupbottomvbox = QVBox(groupsplitter)
+ groupbottomvbox.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
+
+ self.showspecialgroupscheckbox = QCheckBox(i18n("Show system groups"),groupbottomvbox)
+ vbox.setStretchFactor(self.showspecialgroupscheckbox,0)
+ self.connect(self.showspecialgroupscheckbox,SIGNAL("toggled(bool)"), self.slotShowSystemGroupsToggled)
+
+ hbox = QHBox(groupbottomvbox)
+ hbox.setSpacing(KDialog.spacingHint())
+
+ groupsvbox.setStretchFactor(hbox,0)
+
+ self.modifygroupbutton = KPushButton(i18n("Modify..."),hbox)
+ hbox.setStretchFactor(self.modifygroupbutton,1)
+ self.connect(self.modifygroupbutton,SIGNAL("clicked()"),self.slotModifyGroupClicked)
+
+ self.newgroupbutton = KPushButton(i18n("New..."),hbox)
+ hbox.setStretchFactor(self.newgroupbutton,1)
+ self.connect(self.newgroupbutton,SIGNAL("clicked()"),self.slotNewGroupClicked)
+
+ self.deletegroupbutton = KPushButton(i18n("Delete..."),hbox)
+ hbox.setStretchFactor(self.deletegroupbutton,1)
+ self.connect(self.deletegroupbutton,SIGNAL("clicked()"),self.slotDeleteGroupClicked)
+
+ if not isroot:
+ disablebuttons = ( self.modifybutton, self.modifygroupbutton, self.deletebutton, self.deletegroupbutton,
+ self.newbutton, self.newgroupbutton)
+ for widget in disablebuttons:
+ widget.setDisabled(True)
+
+ label = QLabel(i18n("Group Members:"),groupbottomvbox)
+ groupsvbox.setStretchFactor(label,0)
+
+ self.groupmemberlist = KListView(groupbottomvbox)
+ self.groupmemberlist.addColumn(i18n("Login Name"))
+ self.groupmemberlist.addColumn(i18n("Real Name"))
+ self.groupmemberlist.addColumn(i18n("UID"))
+ self.groupmemberlist.setAllColumnsShowFocus(True)
+
+ if not standalone:
+ tabcontrol.addTab(groupsvbox,i18n("Groups"))
+
+ self.admincontext = unixauthdb.getContext(isroot)
+
+ self.updatingGUI = True
+
+ self.showspecialcheckbox.setChecked(self.showsystemaccounts)
+ self.showspecialgroupscheckbox.setChecked(self.showsystemgroups)
+
+ self.__updateUserList()
+ self.__updateGroupList()
+ self.updatingGUI = False
+
+ self.usereditdialog = UserEditDialog(None,self.admincontext)
+ self.userdeletedialog = UserDeleteDialog(None,self.admincontext)
+ self.groupeditdialog = GroupEditDialog(None,self.admincontext)
+
+ #######################################################################
+ def exec_loop(self):
+ global programbase
+ self.__loadOptions()
+ self.updatingGUI = True
+ self.__updateUserList()
+ self.__updateGroupList()
+ self.updatingGUI = False
+ programbase.exec_loop(self)
+ self.__saveOptions()
+
+ #######################################################################
+ def slotUser1(self):
+ self.aboutus.show()
+
+ #######################################################################
+ def slotUserContext(self,l,v,p):
+ cmenu = KPopupMenu(self,"MyActions")
+ cmenu.insertItem(i18n("Modify..."), self.slotModifyClicked, 0, 0)
+ cmenu.insertItem(i18n("Delete..."), self.slotDeleteClicked, 0, 1)
+ if not isroot:
+ cmenu.setItemEnabled(0,False)
+ cmenu.setItemEnabled(1,False)
+
+ cmenu.exec_loop(p)
+
+ #######################################################################
+ def slotGroupContext(self,l,v,p):
+ cmenu = KPopupMenu(self,"MyActions")
+ cmenu.insertItem(i18n("Modify..."), self.slotModifyGroupClicked, 0, 0)
+ cmenu.insertItem(i18n("Delete..."), self.slotDeleteGroupClicked, 0, 1)
+ if not isroot:
+ cmenu.setItemEnabled(0,False)
+ cmenu.setItemEnabled(1,False)
+ cmenu.exec_loop(p)
+
+ #######################################################################
+ def sizeHint(self):
+ global programbase
+ size_hint = programbase.sizeHint(self)
+ # Just make the dialog a little shorter by default.
+ size_hint.setHeight(size_hint.height()-200)
+ return size_hint
+
+ #######################################################################
+ def slotCloseButton(self):
+ self.close()
+
+ #######################################################################
+ def slotListClicked(self,item):
+ if self.updatingGUI==False:
+ for userid in self.useridsToListItems:
+ if self.useridsToListItems[userid]==item:
+ self.updatingGUI = True
+ self.__selectUser(userid)
+ self.updatingGUI = False
+ return
+
+ #######################################################################
+ def slotShowSystemToggled(self,on):
+ self.showsystemaccounts = on
+ if self.updatingGUI==False:
+ self.updatingGUI = True
+ self.__updateUserList()
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotModifyClicked(self):
+ self.usereditdialog.showEditUser(self.selecteduserid)
+ self.updatingGUI = True
+ self.__updateUser(self.selecteduserid)
+ self.__selectUser(self.selecteduserid)
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotNewClicked(self):
+ newuid = self.usereditdialog.showNewUser()
+ if newuid!=None:
+ self.updatingGUI = True
+ self.__updateUserList()
+ self.__selectUser(newuid)
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotDeleteClicked(self):
+ if self.userdeletedialog.deleteUser(self.selecteduserid):
+ self.updatingGUI = True
+ self.selecteduserid = None
+ self.__updateUserList()
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotGroupListClicked(self,item):
+ if self.updatingGUI==False:
+ for groupid in self.groupidsToListItems:
+ if groupid and self.groupidsToListItems[groupid]==item:
+ self.updatingGUI = True
+ self.__selectGroup(groupid)
+ self.updatingGUI = False
+ return
+
+ #######################################################################
+ def slotShowSystemGroupsToggled(self,on):
+ self.showsystemgroups = on
+ if self.updatingGUI==False:
+ self.updatingGUI = True
+ self.__updateGroupList()
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotModifyGroupClicked(self):
+ if self.selectedgroupid!=None:
+ if self.groupeditdialog.showEditGroup(self.selectedgroupid):
+ self.__selectGroup(self.selectedgroupid)
+ self.updatingGUI = True
+ self.__updateUser(self.selecteduserid)
+ self.__selectUser(self.selecteduserid)
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotNewGroupClicked(self):
+ newgroupid = self.groupeditdialog.showNewGroup()
+ if newgroupid!=None:
+ self.updatingGUI = True
+ self.__updateGroupList()
+ self.__updateGroupList()
+ self.__selectGroup(newgroupid)
+ self.__updateUser(self.selecteduserid)
+ self.__selectUser(self.selecteduserid)
+ self.updatingGUI = False
+
+ #######################################################################
+ def slotDeleteGroupClicked(self):
+ if self.selectedgroupid!=None:
+ groupobj = self.admincontext.lookupGID(self.selectedgroupid)
+ groupname = groupobj.getGroupname()
+ gid = groupobj.getGID()
+ nummembers = len(groupobj.getUsers())
+
+ message = i18n("Are you sure you want to delete group '%1' (%2)?\nIt currently has %3 members.").arg(groupname).arg(gid).arg(nummembers)
+ if KMessageBox.warningYesNo(self,message,i18n("Delete Group?"))==KMessageBox.Yes:
+ self.admincontext.removeGroup(groupobj)
+ self.admincontext.save()
+ self.updatingGUI = True
+ self.__updateGroupList()
+ self.__updateUser(self.selecteduserid)
+ self.__selectUser(self.selecteduserid)
+ self.updatingGUI = False
+
+ #######################################################################
+ def __updateUserList(self):
+ self.userlist.clear()
+ self.useridsToListItems = {}
+ firstselecteduserid = None
+
+ users = self.admincontext.getUsers()
+
+ for userobj in users:
+ uid = userobj.getUID()
+ if self.showsystemaccounts or not userobj.isSystemUser():
+ lvi = KListViewItem(self.userlist,userobj.getUsername(),userobj.getRealName(),unicode(uid))
+ if userobj.isLocked():
+ lvi.setPixmap(0,UserIcon("hi16-encrypted"))
+ self.useridsToListItems[uid] = lvi
+ if self.selecteduserid==uid:
+ firstselecteduserid = uid
+ elif firstselecteduserid==None:
+ firstselecteduserid = uid
+ self.selecteduserid = firstselecteduserid
+ self.__selectUser(self.selecteduserid)
+ self.userlist.ensureItemVisible(self.userlist.currentItem())
+
+ #######################################################################
+ def __updateUser(self,userid):
+ lvi = self.useridsToListItems[userid]
+ userobj = self.admincontext.lookupUID(userid)
+ lvi.setText(0,userobj.getUsername())
+ lvi.setText(1,userobj.getRealName())
+ lvi.setText(2,unicode(userobj.getUID()))
+ if userobj.isLocked():
+ lvi.setPixmap(0,UserIcon("hi16-encrypted"))
+ else:
+ lvi.setPixmap(0,QPixmap())
+
+ #######################################################################
+ def __selectUser(self,userid):
+ self.selecteduserid = userid
+ # Only go on if there are actual users.
+ if len(self.useridsToListItems)>0:
+ lvi = self.useridsToListItems[userid]
+ self.userlist.setSelected(lvi,True)
+ self.userlist.setCurrentItem(lvi)
+
+ userobj = self.admincontext.lookupUID(userid)
+
+ username = userobj.getUsername()
+ self.loginnamelabel.setText(username)
+ self.realnamelabel.setText(userobj.getRealName())
+ self.uidlabel.setText(unicode(userid))
+ if userobj.isLocked():
+ self.statuslabel.setText(i18n("Disabled"))
+ else:
+ self.statuslabel.setText(i18n("Enabled"))
+
+ # Primary Group
+ primarygroupobj = userobj.getPrimaryGroup()
+ primarygroupname = primarygroupobj.getGroupname()
+ self.primarygrouplabel.setText(primarygroupname)
+
+ # Secondary Groups
+ secondarygroups = [g.getGroupname() for g in userobj.getGroups() if g is not userobj.getPrimaryGroup()]
+ self.secondarygrouplabel.setText(unicode(i18n(", ")).join(secondarygroups))
+
+ if isroot:
+ self.deletebutton.setDisabled(userobj.getUID()==0)
+
+ #######################################################################
+ def __updateGroupList(self):
+ self.grouplist.clear()
+ self.groupidsToListItems = {}
+ firstselectedgroupid = None
+
+ groups = self.admincontext.getGroups()
+ for groupobj in groups:
+ gid = groupobj.getGID()
+ if self.showsystemgroups or not groupobj.isSystemGroup():
+ lvi = QListViewItem(self.grouplist,groupobj.getGroupname(),unicode(gid))
+ self.groupidsToListItems[gid] = lvi
+ if self.selectedgroupid==gid:
+ firstselectedgroupid = gid
+ elif firstselectedgroupid==None:
+ firstselectedgroupid = gid
+ self.selectedgroupid = firstselectedgroupid
+ self.__selectGroup(self.selectedgroupid)
+ self.grouplist.ensureItemVisible(self.grouplist.currentItem())
+
+ #######################################################################
+ def __selectGroup(self,groupid):
+ if groupid:
+ self.selectedgroupid = groupid
+ lvi = self.groupidsToListItems[groupid]
+ self.grouplist.setSelected(lvi,True)
+ self.grouplist.setCurrentItem(lvi)
+
+ groupobj = self.admincontext.lookupGID(groupid)
+ members = groupobj.getUsers()
+ self.groupmemberlist.clear()
+ for userobj in members:
+ if userobj!=None:
+ lvi = QListViewItem(self.groupmemberlist,userobj.getUsername(),userobj.getRealName(),unicode(userobj.getUID()))
+ if isroot:
+ self.deletegroupbutton.setDisabled(groupobj.getGID()==0)
+
+ #######################################################################
+ def __loadOptions(self):
+ self.config.setGroup("General")
+ size = self.config.readSizeEntry("Geometry")
+ if size.isEmpty()==False:
+ self.resize(size)
+ self.config.setGroup("Options")
+ self.showsystemaccounts = self.config.readBoolEntry("ShowSystemAccounts")
+ self.showspecialcheckbox.setChecked(self.showsystemaccounts)
+ self.showsystemgroups = self.config.readBoolEntry("ShowSystemGroups")
+ self.showspecialgroupscheckbox.setChecked(self.showsystemgroups)
+
+ #######################################################################
+ def __saveOptions(self):
+ global isroot
+ if isroot:
+ return
+ self.config.setGroup("General")
+ self.config.writeEntry("Geometry", self.size())
+ self.config.setGroup("Options")
+ self.config.writeEntry("ShowSystemAccounts",self.showsystemaccounts)
+ self.config.writeEntry("ShowSystemGroups",self.showsystemgroups)
+ self.config.sync()
+
+ #######################################################################
+ # KControl virtual void methods
+ def load(self):
+ pass
+ def save(self):
+ pass
+ def defaults(self):
+ pass
+ def sysdefaults(self):
+ pass
+
+ def aboutData(self):
+ # Return the KAboutData object which we created during initialisation.
+ return self.aboutdata
+ def buttons(self):
+ # Only supply a Help button. Other choices are Default and Apply.
+ return KCModule.Help
+
+###########################################################################
+
+# Rudd-O convenience class to map groups to privilege names
+class PrivilegeNames(dict):
+ """Convenience dict-derived class: map known secondary groups to privilege names, provide default mapping for groups that do not have a description. This could be replaced by a simple dict() but I simply preferred the class declaration.
+
+ FIXME This should ideally be included in a more general module so it can be reused."""
+
+ def __init__(self):
+ dict.__init__(self, {
+ "plugdev":i18n("Access external storage devices automatically"),
+ "adm":i18n("Administer the system"),
+ "ltsp":i18n("Allow use of FUSE filesystems like LTSP thin client block devices"),
+ "dialout":i18n("Connect to the Internet using a modem"),
+ "syslog":i18n("Monitor system logs"),
+ "fax":i18n("Send and receive faxes"),
+ "cdrom":i18n("Use CD-ROM and DVD drives"),
+ "floppy":i18n("Use floppy drives"),
+ "modem":i18n("Use modems"),
+ "scanner":i18n("Use scanners"),
+ })
+
+ def __getitem__(self,name):
+ # This is cruft but I couldn't bring myself to kill it bua!
+ if name in self: return dict.__getitem__(self,name)
+ return i18n("Be a member of the %s group")%name
+
+class UserEditDialog(KDialogBase):
+ def __init__(self,parent,admincontext):
+ KDialogBase.__init__(self,KJanusWidget.Tabbed,i18n("User Account"),KDialogBase.Ok|KDialogBase.Cancel,
+ KDialogBase.Cancel,parent)
+
+ self.admincontext = admincontext
+ self.updatingGUI = True
+
+ detailsvbox = self.addHBoxPage(i18n("Details"))
+ detailspace = QWidget(detailsvbox)
+
+ infogrid = QGridLayout(detailspace,9,2)
+ infogrid.setSpacing(self.spacingHint())
+ infogrid.setColStretch(0,0)
+ infogrid.setColStretch(1,1)
+
+ self.enabledradiogroup = QButtonGroup()
+ self.enabledradiogroup.setRadioButtonExclusive(True)
+ hb = QHBox(detailspace)
+ hb.setSpacing(self.spacingHint())
+ label = QLabel(hb)
+ label.setPixmap(UserIcon("hi32-identity"))
+ hb.setStretchFactor(label,0)
+ label = QLabel(i18n("Status:"),hb)
+ hb.setStretchFactor(label,1)
+ infogrid.addMultiCellWidget(hb,0,1,0,0)
+
+ self.enabledradio = QRadioButton(i18n("Enabled"),detailspace)
+ infogrid.addWidget(self.enabledradio,0,1)
+
+ hbox = QHBox(detailspace)
+ hbox.setSpacing(self.spacingHint())
+ self.disabledradio = QRadioButton(i18n("Disabled"),hbox)
+ hbox.setStretchFactor(self.disabledradio,0)
+ label = QLabel(hbox)
+ label.setPixmap(UserIcon("hi16-encrypted"))
+ hbox.setStretchFactor(label,1)
+ infogrid.addWidget(hbox,1,1)
+
+ self.enabledradiogroup.insert(self.enabledradio,0)
+ self.enabledradiogroup.insert(self.disabledradio,1)
+
+ label = QLabel(i18n("Login Name:"),detailspace)
+ infogrid.addWidget(label,2,0)
+ self.loginnameedit = KLineEdit("",detailspace)
+ self.loginnameedit.setValidator(LoginNameValidator(self.loginnameedit))
+
+ infogrid.addWidget(self.loginnameedit,2,1)
+ self.connect(self.loginnameedit, SIGNAL("textChanged(const QString &)"), self.slotLoginChanged)
+
+ label = QLabel(i18n("Real Name:"),detailspace)
+ infogrid.addWidget(label,3,0)
+ self.realnameedit = KLineEdit("",detailspace)
+ self.realnameedit.setValidator(RealUserNameValidator(self.realnameedit))
+
+ infogrid.addWidget(self.realnameedit,3,1)
+
+ label = QLabel(i18n("User ID:"),detailspace)
+ infogrid.addWidget(label,4,0)
+ self.uidedit = KLineEdit("",detailspace)
+ self.uidedit.setValidator(QIntValidator(0,65535,detailspace))
+ infogrid.addWidget(self.uidedit,4,1)
+
+ label = QLabel(i18n("Primary Group:"),detailspace)
+ infogrid.addWidget(label,5,0)
+ self.primarygroupedit = KComboBox(False,detailspace)
+ infogrid.addWidget(self.primarygroupedit,5,1)
+
+ label = QLabel(i18n("Home Directory:"),detailspace)
+ infogrid.addWidget(label,7,0)
+
+ hbox = QHBox(detailspace)
+ hbox.setSpacing(self.spacingHint())
+ self.homediredit = KLineEdit("",hbox)
+ hbox.setStretchFactor(self.homediredit,1)
+ self.connect(self.homediredit, SIGNAL("textChanged(const QString &)"), self.slotHomeDirChanged)
+ self.homedirbutton = KPushButton(i18n("Browse..."),hbox)
+ hbox.setStretchFactor(self.homedirbutton,0)
+ self.connect(self.homedirbutton,SIGNAL("clicked()"),self.slotBrowseHomeDirClicked)
+ infogrid.addWidget(hbox,7,1)
+
+ label = QLabel(i18n("Shell:"),detailspace)
+ infogrid.addWidget(label,8,0)
+
+ self.shelledit = KComboBox(True,detailspace)
+ for shell in self.admincontext.getUserShells():
+ self.shelledit.insertItem(shell)
+ infogrid.addWidget(self.shelledit,8,1)
+
+ # Rudd-O rules. Not so much, but enough to rule.
+ # yeah it's not my finest hour, but it works like a charm over here. Please feel free to clean up dead code that I commented
+ # I extend my deepest thanks to the people that have worked hard to construct this tool in the first place. I have no idea who the authors and contributors are, but it would make sense to have all the contributors listed on top of the file.
+ # Privileges and groups tab
+ groupsvbox = self.addHBoxPage(i18n("Privileges and groups"))
+
+ # Rudd-O now here we create the widget that will hold the group listing, and fill it with the groups.
+ self.privilegeslistview = QListView(groupsvbox)
+ self.privilegeslistview.addColumn(i18n("Privilege"),-1)
+ self.groupslistview = QListView(groupsvbox)
+ self.groupslistview.addColumn(i18n("Secondary group"),-1)
+ groupsvbox.setStretchFactor(self.privilegeslistview,3)
+ groupsvbox.setStretchFactor(self.groupslistview,2)
+
+ # Password and Security Tab.
+ passwordvbox = self.addVBoxPage(i18n("Password && Security"))
+
+ passwordspace = QWidget(passwordvbox)
+ passwordgrid = QGridLayout(passwordspace,8,3)
+ passwordgrid.setSpacing(self.spacingHint())
+ passwordgrid.setColStretch(0,0)
+ passwordgrid.setColStretch(1,0)
+ passwordgrid.setColStretch(2,1)
+ passwordvbox.setStretchFactor(passwordspace,0)
+
+ hb = QHBox(passwordspace)
+ hb.setSpacing(self.spacingHint())
+ label = QLabel(hb)
+ label.setPixmap(UserIcon("hi32-password"))
+ hb.setStretchFactor(label,0)
+ label = QLabel(i18n("Password:"),hb)
+ hb.setStretchFactor(label,1)
+ passwordgrid.addWidget(hb,0,0)
+
+ self.passwordedit = KPasswordEdit(passwordspace)
+ passwordgrid.addWidget(self.passwordedit,0,1)
+
+ # Last Change
+ label = QLabel(i18n("Last changed:"),passwordspace)
+ passwordgrid.addWidget(label,1,0)
+ self.lastchangelabel = KLineEdit("",passwordspace)
+ self.lastchangelabel.setReadOnly(True)
+ passwordgrid.addWidget(self.lastchangelabel,1,1)
+
+ self.validradiogroup = QButtonGroup()
+ self.validradiogroup.setRadioButtonExclusive(True)
+
+ # Valid until.
+ label = QLabel(i18n("Valid until:"),passwordspace)
+ passwordgrid.addWidget(label,2,0)
+ self.validalwaysradio = QRadioButton(i18n("Always"),passwordspace)
+ passwordgrid.addWidget(self.validalwaysradio,2,1)
+
+ hbox = QHBox(passwordspace)
+ hbox.setSpacing(self.spacingHint())
+ self.expireradio = QRadioButton(hbox)
+ hbox.setStretchFactor(self.expireradio,0)
+
+ self.expiredate = KDateWidget(hbox)
+ hbox.setStretchFactor(self.expiredate,1)
+ passwordgrid.addWidget(hbox,3,1)
+
+ self.validradiogroup.insert(self.validalwaysradio,0)
+ self.validradiogroup.insert(self.expireradio,1)
+ self.connect(self.validradiogroup,SIGNAL("clicked(int)"),self.slotValidUntilClicked)
+
+ # Password Aging & Expiration.
+ passwordaginggroup = QVGroupBox(i18n("Password Aging"),passwordvbox)
+ passwordaginggroup.setInsideSpacing(self.spacingHint())
+ passwordvbox.setStretchFactor(passwordaginggroup,0)
+
+ passwordagingwidget = QWidget(passwordaginggroup)
+
+ passwordaginggrid = QGridLayout(passwordagingwidget,4,3)
+ passwordaginggrid.setSpacing(self.spacingHint())
+
+ # [*] Require new password after: [_____5 days]
+ self.forcepasswordchangecheckbox = QCheckBox(passwordagingwidget)
+ self.connect(self.forcepasswordchangecheckbox,SIGNAL("toggled(bool)"),self.slotForcePasswordChangeToggled)
+ passwordaginggrid.addWidget(self.forcepasswordchangecheckbox,0,0)
+ label = QLabel(i18n("Require new password after:"),passwordagingwidget)
+ passwordaginggrid.addWidget(label,0,1)
+ self.maximumpasswordedit = QSpinBox(passwordagingwidget)
+ self.maximumpasswordedit.setSuffix(i18n(" days"))
+ self.maximumpasswordedit.setMinValue(1)
+ self.maximumpasswordedit.setMaxValue(365*5)
+ passwordaginggrid.addWidget(self.maximumpasswordedit,0,2)
+
+ label = QLabel(i18n("Warn before password expires:"),passwordagingwidget)
+ passwordaginggrid.addWidget(label,1,1)
+ self.warningedit = QSpinBox(passwordagingwidget)
+ self.warningedit.setPrefix(i18n("After "))
+ self.warningedit.setSuffix(i18n(" days"))
+ self.warningedit.setMinValue(0)
+ self.warningedit.setMaxValue(365*5)
+ self.warningedit.setSpecialValueText(i18n("Never"))
+ passwordaginggrid.addWidget(self.warningedit,1,2)
+
+ label = QLabel(i18n("Disable account after password expires:"),passwordagingwidget)
+ passwordaginggrid.addWidget(label,2,1)
+ self.disableexpireedit = QSpinBox(passwordagingwidget)
+ self.disableexpireedit.setPrefix(i18n("After "))
+ self.disableexpireedit.setSuffix(i18n(" days"))
+ self.disableexpireedit.setMinValue(0)
+ self.disableexpireedit.setMaxValue(365*5)
+ self.disableexpireedit.setSpecialValueText(i18n("Never"))
+ passwordaginggrid.addWidget(self.disableexpireedit,2,2)
+
+ self.enforcepasswordminagecheckbox = QCheckBox(passwordagingwidget)
+ self.connect(self.enforcepasswordminagecheckbox,SIGNAL("toggled(bool)"),self.slotEnforePasswordAgeToggled)
+ passwordaginggrid.addWidget(self.enforcepasswordminagecheckbox,3,0)
+
+ label = QLabel(i18n("Enforce minimum password age:"),passwordagingwidget)
+ passwordaginggrid.addWidget(label,3,1)
+ self.minimumpasswordedit = QSpinBox(passwordagingwidget)
+ self.minimumpasswordedit.setSuffix(i18n(" days"))
+ passwordaginggrid.addWidget(self.minimumpasswordedit,3,2)
+
+ spacer = QWidget(passwordvbox)
+ passwordvbox.setStretchFactor(spacer,1)
+
+ self.homedirdialog = KDirSelectDialog("/",True,self,"Select Home Directory",True)
+ self.createhomedirectorydialog = OverwriteHomeDirectoryDialog(None)
+ self.updatingGUI = False
+
+ def _repopulateGroupsPrivileges(self,excludegroups=None):
+ # needs listviews to be constructed. Expects a list of PwdGroups to be excluded
+
+ # rehash everything
+ self.privilegeslistview.clear()
+ self.groupslistview.clear()
+ self.secondarygroupcheckboxes = {}
+ pn = PrivilegeNames()
+
+ if excludegroups: excludegroups = [ g.getGroupname() for g in excludegroups ]
+ else: excludegroups = []
+ for group in [g.getGroupname() for g in self.admincontext.getGroups()]:
+ if group in excludegroups: continue
+ if group in pn:
+ name = i18n(unicode(pn[group]).encode(locale.getpreferredencoding()))
+ wid = self.privilegeslistview
+ else:
+ name = unicode(group).encode(locale.getpreferredencoding())
+ wid = self.groupslistview
+ self.secondarygroupcheckboxes[group] = QCheckListItem(wid,name,QCheckListItem.CheckBox)
+
+ ########################################################################
+ def showEditUser(self,userid):
+ self.updatingGUI = True
+ self.newusermode = False
+ self.userobj = self.admincontext.lookupUID(userid)
+ self.userid = userid
+ self.passwordedit.erase()
+ self.selectedgroups = [g.getGroupname() for g in self.userobj.getGroups()
+ if g is not self.userobj.getPrimaryGroup()]
+
+ # Rudd-O: now here we tick the appropriate group listing checkbox, and hide the currently active primary group of the user. We are repopulating because if the user to edit changes, we need to hide the user's secondary group. FIXME we should repopulate the groups privileges list when the primary group is changed in the other tab -- that is, on the change slot of the primary group drop down.
+ self._repopulateGroupsPrivileges(excludegroups=[self.userobj.getPrimaryGroup()])
+ for group,checkbox in self.secondarygroupcheckboxes.items():
+ if group in self.selectedgroups: checkbox.setState(QCheckListItem.On)
+ else: checkbox.setState(QCheckListItem.Off)
+
+ self.originalgroups = self.selectedgroups[:]
+ self.selectedgroups.sort()
+ self.__syncGUI()
+ self.uidedit.setReadOnly(True)
+ self.updatingGUI = False
+ self.homedirectoryislinked = False
+ if self.exec_loop()==QDialog.Accepted:
+ self.__updateObjectFromGUI(self.userobj)
+ # Set the password.
+ if self.passwordedit.password()!="":
+ self.userobj.setPassword(self.passwordedit.password())
+ # Update the groups for this user object. Rudd-O here's when you go in, stud.
+ # we collect the selected groups
+ self.selectedgroups = [ group for group,checkbox in self.secondarygroupcheckboxes.items() if checkbox.isOn() ]
+
+ for g in self.userobj.getGroups(): # this seems wasteful to remove the user from all groups then re-add, why not a cross check?
+ self.userobj.removeFromGroup(g)
+ for gn in self.selectedgroups:
+ self.userobj.addToGroup(self.admincontext.lookupGroupname(gn))
+
+ primarygroupname = unicode(self.primarygroupedit.currentText())
+ self.userobj.setPrimaryGroup(self.admincontext.lookupGroupname(primarygroupname))
+
+ # Enable/Disable the account
+ self.userobj.setLocked(self.enabledradiogroup.id(self.enabledradiogroup.selected())!=0)
+ self.admincontext.save()
+
+ ########################################################################
+ def showNewUser(self):
+ self.updatingGUI = True
+ self.newusermode = True
+ self.userobj = self.admincontext.newUser(True)
+
+ self.newgroup = self.admincontext.newGroup(True)
+ self.newgroup.setGroupname(self.__fudgeNewGroupName(self.userobj.getUsername()))
+ self.userobj.setPrimaryGroup(self.newgroup)
+
+ self.selectedgroups = [ u'dialout',u'cdrom',u'floppy',u'audio',u'video',
+ u'plugdev',u'lpadmin',u'scanner']
+ homedir = self.__fudgeNewHomeDirectory(self.userobj.getUsername())
+
+ # Rudd-O FIXME: now here we tick the proper groups that should be allowed. Now it selects what userconfig selected before. FIXME consider adding a drop down that will select the appropriate profile Limited User, Advanced User or Administrator (and see if there is a config file where these profiles can be read). We are repopulating because if the user to edit changes, we need to hide the user's secondary group. FIXME we should repopulate the groups privileges list when the primary group is changed in the other tab -- that is, on the change slot of the primary group drop down.
+ self._repopulateGroupsPrivileges()
+ for group,checkbox in self.secondarygroupcheckboxes.items():
+ if group in self.selectedgroups: checkbox.setState(QCheckListItem.On)
+ else: checkbox.setState(QCheckListItem.Off)
+
+ self.userobj.setHomeDirectory(homedir)
+ self.homediredit.setText(homedir)
+
+ shells = self.admincontext.getUserShells()
+ dshell = self.admincontext.dshell
+ if dshell and (dshell in shells):
+ self.userobj.setLoginShell(dshell)
+ elif '/bin/bash' in shells:
+ self.userobj.setLoginShell('/bin/bash')
+ elif '/bin/sh' in shells:
+ self.userobj.setLoginShell('/bin/sh')
+ elif len(shells)!=0:
+ self.userobj.setLoginShell(shells[0])
+
+ self.__syncGUI()
+
+ self.uidedit.setReadOnly(False)
+ self.updatingGUI = False
+ self.homedirectoryislinked = True
+ self.passwordedit.erase()
+ if self.exec_loop()==QDialog.Accepted:
+ self.__updateObjectFromGUI(self.userobj)
+
+ makehomedir = True
+ deleteoldhomedir = False
+
+ if os.path.exists(self.userobj.getHomeDirectory()):
+ rc = self.createhomedirectorydialog.do(self.userobj)
+ if rc==OverwriteHomeDirectoryDialog.CANCEL:
+ return None
+ if rc==OverwriteHomeDirectoryDialog.OK_KEEP:
+ makehomedir = False
+ else:
+ deleteoldhomedir = True
+
+ self.admincontext.addUser(self.userobj)
+
+ if self.admincontext.lookupGroupname(self.primarygroupname) is None:
+ # Create a new group
+ newgroup = self.admincontext.newGroup(True)
+ newgroup.setGroupname(self.primarygroupname)
+ self.admincontext.addGroup(newgroup)
+ self.userobj.setPrimaryGroup(newgroup)
+
+ # Update the groups for this user object. Rudd-O here's when you go in, stud.
+ # we collect the selected groups
+ self.selectedgroups = [ group for group,checkbox in self.secondarygroupcheckboxes.items() if checkbox.isOn() ]
+ for gn in self.selectedgroups:
+ self.userobj.addToGroup(self.admincontext.lookupGroupname(gn))
+
+ # Set the password.
+ if self.passwordedit.password()!="":
+ self.userobj.setPassword(self.passwordedit.password())
+
+ # Enable/Disable the account
+ self.userobj.setLocked(self.enabledradiogroup.id(self.enabledradiogroup.selected())!=0)
+ self.admincontext.save()
+
+ if deleteoldhomedir:
+ if os.path.exists(self.userobj.getHomeDirectory()):
+ shutil.rmtree(self.userobj.getHomeDirectory())
+ if makehomedir:
+ self.admincontext.createHomeDirectory(self.userobj)
+
+ return self.userobj.getUID()
+ else:
+ return None
+
+ ########################################################################
+ def slotOk(self):
+ ok = True
+ # Sanity check all values.
+ if self.newusermode:
+ newusername = unicode(self.realnameedit.text())
+ if self.admincontext.lookupUsername(newusername)!=None:
+ KMessageBox.sorry(self,i18n("Sorry, you must choose a different user name.\n'%1' is already being used.").arg(newusername))
+ ok = False
+ else:
+ newuid = int(unicode(self.uidedit.text()))
+ originaluid = self.userobj.getUID()
+ if self.admincontext.lookupUID(newuid)!=None:
+ rc = KMessageBox.questionYesNo(self,i18n("User ID in use"),
+ i18n("Sorry, the UID %1 is already in use. Should %2 be used instead?").arg(newuid).arg(originaluid))
+ if rc==KMessageBox.Yes:
+ self.uidedit.setValue(unicode(originaluid))
+ else:
+ ok = False
+ else:
+ self.userobj.setUID(newuid)
+ if ok:
+ self.passwordedit.clear()
+ KDialogBase.slotOk(self)
+
+ ########################################################################
+ def slotLoginChanged(self,text):
+ newtext = unicode(text)
+ if not self.updatingGUI:
+ if self.newusermode:
+ self.newprimarygroupname = self.__fudgeNewGroupName(newtext)
+ self.updatingGUI = True
+ self.primarygroupedit.changeItem(self.newprimarygroupname,0)
+ if self.homedirectoryislinked:
+ homedir = self.__fudgeNewHomeDirectory(newtext)
+ self.homediredit.setText(homedir)
+ self.updatingGUI = False
+
+ ########################################################################
+ def slotHomeDirChanged(self,text):
+ if self.updatingGUI==False:
+ self.homedirectoryislinked = False
+
+ ########################################################################
+ def __syncGUI(self):
+ if self.userobj.isLocked():
+ self.enabledradiogroup.setButton(1)
+ else:
+ self.enabledradiogroup.setButton(0)
+
+ self.loginnameedit.setText(self.userobj.getUsername())
+ self.realnameedit.setText(self.userobj.getRealName())
+ self.uidedit.setText(unicode(self.userobj.getUID()))
+ self.homediredit.setText(self.userobj.getHomeDirectory())
+ self.shelledit.setCurrentText(self.userobj.getLoginShell())
+
+ # Primary Group
+ self.primarygroupedit.clear()
+ allgroups = [g.getGroupname() for g in self.admincontext.getGroups()]
+ allgroups.sort()
+ self.availablegroups = allgroups[:]
+
+ try:
+ self.availablegroups.remove(self.userobj.getPrimaryGroup().getGroupname())
+ except ValueError:
+ pass
+
+ if self.newusermode:
+ # New user mode
+ self.newprimarygroupname = self.__fudgeNewGroupName(unicode(self.userobj.getUsername()))
+ primarygroupname = self.newprimarygroupname
+ self.primarygroupedit.insertItem(self.newprimarygroupname)
+ else:
+ # Existing user mode
+ primarygroupname = self.userobj.getPrimaryGroup().getGroupname()
+ for group in allgroups:
+ self.primarygroupedit.insertItem(group)
+ self.primarygroupedit.setCurrentText(primarygroupname)
+
+ # If ShadowExpire is turn off then we change the radio box.
+ if self.userobj.getExpirationDate() is None:
+ self.validradiogroup.setButton(0)
+ self.expiredate.setDisabled(True)
+ self.expiredate.setDate(SptimeToQDate(99999L))
+ else:
+ self.validradiogroup.setButton(1)
+ self.expiredate.setDisabled(False)
+ self.expiredate.setDate(SptimeToQDate(self.userobj.getExpirationDate()))
+
+ if self.userobj.getMaximumPasswordAge() is None:
+ # Password aging is turn off
+ self.forcepasswordchangecheckbox.setChecked(False)
+ d = True
+ else:
+ # Password aging is turn on
+ self.forcepasswordchangecheckbox.setChecked(True)
+ d = False
+ self.warningedit.setDisabled(d)
+ self.maximumpasswordedit.setDisabled(d)
+ self.disableexpireedit.setDisabled(d)
+
+ if self.userobj.getPasswordExpireWarning() is None:
+ self.warningedit.setValue(0)
+ else:
+ self.warningedit.setValue(self.userobj.getPasswordExpireWarning())
+
+ if self.userobj.getMaximumPasswordAge() is None:
+ self.maximumpasswordedit.setValue(30)
+ else:
+ self.maximumpasswordedit.setValue(self.userobj.getMaximumPasswordAge())
+
+ if self.userobj.getPasswordDisableAfterExpire() is None:
+ self.disableexpireedit.setValue(0)
+ else:
+ self.disableexpireedit.setValue(self.userobj.getPasswordDisableAfterExpire())
+
+ minage = self.userobj.getMinimumPasswordAgeBeforeChange()
+ self.enforcepasswordminagecheckbox.setChecked(minage>0)
+ self.minimumpasswordedit.setDisabled(minage<=0)
+ if minage<=0:
+ minage = 1
+ self.minimumpasswordedit.setValue(minage)
+
+ if self.userobj.getLastPasswordChange() in (None,0):
+ self.lastchangelabel.setText('-');
+ else:
+ self.lastchangelabel.setText(KGlobal.locale().formatDate(SptimeToQDate(int(self.userobj.getLastPasswordChange()))))
+
+ ########################################################################
+ def __updateObjectFromGUI(self,userobj):
+ username = unicode(self.loginnameedit.text())
+ userobj.setUsername(username)
+ userobj.setRealName(unicode(self.realnameedit.text()))
+
+ userobj.setHomeDirectory(unicode(self.homediredit.text()))
+ userobj.setLoginShell(unicode(self.shelledit.currentText()))
+ self.primarygroupname = unicode(self.primarygroupedit.currentText())
+ groupobj = self.admincontext.lookupGroupname(self.primarygroupname)
+ if groupobj is not None:
+ userobj.setPrimaryGroup(groupobj)
+
+ # Password expiration.
+ if self.validradiogroup.id(self.validradiogroup.selected())==0:
+ # Password is always valid.
+ userobj.setExpirationDate(None)
+ else:
+ # Password will expire at...
+ userobj.setExpirationDate(QDateToSptime(self.expiredate.date()))
+
+ if self.forcepasswordchangecheckbox.isChecked():
+ userobj.setMaximumPasswordAge(self.maximumpasswordedit.value())
+ else:
+ userobj.setMaximumPasswordAge(None)
+
+ if self.disableexpireedit.value()==0:
+ userobj.setPasswordDisableAfterExpire(None)
+ else:
+ userobj.setPasswordDisableAfterExpire(self.disableexpireedit.value())
+
+ if self.enforcepasswordminagecheckbox.isChecked():
+ userobj.setMinimumPasswordAgeBeforeChange(self.minimumpasswordedit.value())
+ else:
+ userobj.setMinimumPasswordAgeBeforeChange(0)
+
+ userobj.setPasswordExpireWarning(self.warningedit.value())
+
+ ########################################################################
+ def slotBrowseHomeDirClicked(self):
+ fileurl = KURL()
+ fileurl.setPath(self.homediredit.text())
+ self.homedirdialog.setCurrentURL(fileurl)
+ if self.homedirdialog.exec_loop()==QDialog.Accepted:
+ self.homediredit.setText(self.homedirdialog.url().path())
+ self.homedirectoryislinked = False
+
+ ########################################################################
+ def slotValidUntilClicked(self,id):
+ if id==0:
+ self.expiredate.setDisabled(True)
+ else:
+ self.expiredate.setDisabled(False)
+
+ ########################################################################
+ def slotForcePasswordChangeToggled(self,on):
+ on = not on
+ self.warningedit.setDisabled(on)
+ self.maximumpasswordedit.setDisabled(on)
+ self.disableexpireedit.setDisabled(on)
+
+ ########################################################################
+ def slotEnforePasswordAgeToggled(self,on):
+ self.minimumpasswordedit.setDisabled(not on)
+
+ #######################################################################
+ def __fudgeNewGroupName(self,basename):
+ if self.admincontext.lookupGroupname(basename) is None:
+ return basename
+ x = 1
+ while self.admincontext.lookupGroupname(basename + u'_' + unicode(x)) is not None:
+ x += 1
+ return basename + u'_' + unicode(x)
+
+ #######################################################################
+ def __fudgeNewHomeDirectory(self,origbasename):
+ basename = origbasename.replace("/","")
+ if basename=="":
+ basename = u"user"
+
+ dhome = self.admincontext.dhome
+ if not os.path.isdir(dhome):
+ raise OSError, dhome+" does not exist, is it correctly set in "+ \
+ self.admincontext.adduserconf+" ?"
+ else:
+ # Make sure there's a trailing /
+ if dhome[-1] is not '/':
+ dhome = dhome+'/'
+
+ if os.path.exists(dhome+basename)==False:
+ return dhome+basename
+ else:
+ x = 1
+ while os.path.exists(dhome+basename + u'_' + unicode(x)):
+ x += 1
+ return dhome+basename
+
+
+###########################################################################
+class LoginNameValidator(QValidator):
+ def __init__(self,parent):
+ QValidator.__init__(self,parent)
+
+ #######################################################################
+ def validate(self,inputstr,pos):
+ instr = unicode(inputstr)
+ if len(instr)==0:
+ return (QValidator.Intermediate,pos)
+ for c in instr:
+ if ord(c)<0x20 or ord(c)>0x7f or c.isspace() or c==":" or c=="," or c==".":
+ return (QValidator.Invalid,pos)
+
+ # Try to encode this string in the system encoding.
+ try:
+ instr.encode(locale.getpreferredencoding())
+ except UnicodeEncodeError:
+ # won't encode -> reject it.
+ return (QValidator.Invalid,pos)
+
+ return (QValidator.Acceptable,pos)
+
+ #######################################################################
+ def fixup(self,inputstr):
+ instr = unicode(inputstr)
+ newstr = ""
+ for c in instr:
+ if (ord(c)<0x20 or ord(c)>0x7f or c.isspace() or c==":" or c=="," or c==".")==False:
+ newstr += c
+
+ if newstr=="":
+ return "user"
+ else:
+ return newstr
+
+###########################################################################
+class RealUserNameValidator(QValidator):
+ def __init__(self,parent):
+ QValidator.__init__(self,parent)
+
+ #######################################################################
+ def validate(self,inputstr,pos):
+ instr = unicode(inputstr)
+ for c in instr:
+ if c==":":
+ return (QValidator.Invalid,pos)
+
+ # Try to encode this string in the system encoding.
+ try:
+ instr.encode(locale.getpreferredencoding())
+ except UnicodeEncodeError:
+ # won't encode -> reject it.
+ return (QValidator.Invalid,pos)
+
+ return (QValidator.Acceptable,pos)
+
+ #######################################################################
+ def fixup(self,inputstr):
+ return unicode(inputstr).replace(":","")
+
+###########################################################################
+class ListPickerDialog(KDialogBase):
+ def __init__(self,parent,caption,leftlabel,rightlabel):
+ KDialogBase.__init__(self,parent,None,True,caption,KDialogBase.Ok|KDialogBase.Cancel, KDialogBase.Cancel)
+
+ self.tophbox = QHBox(self)
+ self.setMainWidget(self.tophbox)
+ self.tophbox.setSpacing(self.spacingHint())
+ # Available Groups
+ vbox = QVBox(self.tophbox)
+ self.tophbox.setStretchFactor(vbox,1)
+ label = QLabel(leftlabel,vbox)
+ vbox.setStretchFactor(label,0)
+ self.availablelist = KListBox(vbox)
+ vbox.setStretchFactor(self.availablelist,1)
+
+ # ->, <- Buttons
+ vbox = QVBox(self.tophbox)
+ self.tophbox.setStretchFactor(vbox,0)
+ spacer = QWidget(vbox);
+ vbox.setStretchFactor(spacer,1)
+ self.addbutton = KPushButton(i18n("Add ->"),vbox)
+ self.connect(self.addbutton,SIGNAL("clicked()"),self.slotAddClicked)
+ vbox.setStretchFactor(self.addbutton,0)
+ self.removebutton = KPushButton(i18n("<- Remove"),vbox)
+ self.connect(self.removebutton,SIGNAL("clicked()"),self.slotRemoveClicked)
+ vbox.setStretchFactor(self.removebutton,0)
+ spacer = QWidget(vbox);
+ vbox.setStretchFactor(spacer,1)
+
+ # Selected Groups
+ vbox = QVBox(self.tophbox)
+ self.tophbox.setStretchFactor(vbox,1)
+ label = QLabel(rightlabel,vbox)
+ vbox.setStretchFactor(label,0)
+ self.selectedlist = KListBox(vbox)
+ vbox.setStretchFactor(self.selectedlist,1)
+
+ #######################################################################
+ def do(self,grouplist,selectedlist):
+ self.selectedlist.clear()
+ for item in selectedlist:
+ self.selectedlist.insertItem(item)
+ self.selectedlist.sort()
+
+ self.availablelist.clear()
+ for item in grouplist:
+ if item not in selectedlist:
+ self.availablelist.insertItem(item)
+ self.availablelist.sort()
+
+ self._selectFirstAvailable()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+
+ self._selectFirstSelected()
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ if self.exec_loop()==QDialog.Accepted:
+ newlist = []
+ for i in range(self.selectedlist.count()):
+ newlist.append(unicode(self.selectedlist.item(i).text()))
+ return newlist
+ else:
+ return selectedlist
+
+ #######################################################################
+ def slotAddClicked(self):
+ item = self.availablelist.selectedItem()
+ if item!=None:
+ self.selectedlist.insertItem(item.text())
+ self.availablelist.removeItem(self.availablelist.index(item))
+ self._selectFirstAvailable()
+ self._selectFirstSelected()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ #######################################################################
+ def slotRemoveClicked(self):
+ item = self.selectedlist.selectedItem()
+ if item!=None:
+ self.availablelist.insertItem(item.text())
+ self.selectedlist.removeItem(self.selectedlist.index(item))
+ self._selectFirstAvailable()
+ self._selectFirstSelected()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ #######################################################################
+ def _selectFirstAvailable(self):
+ if self.availablelist.count()!=0:
+ if self.availablelist.selectedItem()==None:
+ self.availablelist.setSelected(0,True)
+
+ #######################################################################
+ def _selectFirstSelected(self):
+ if self.selectedlist.count()!=0:
+ if self.selectedlist.selectedItem()==None:
+ self.selectedlist.setSelected(0,True)
+
+###########################################################################
+class UserDeleteDialog(KDialog):
+ def __init__(self,parent,admincontext):
+ KDialog.__init__(self,parent,"Delete user dialog",True,Qt.WType_Dialog)
+ self.setCaption(i18n("Delete User Account"))
+ self.admincontext = admincontext
+ self.updatingGUI = True
+
+ toplayout = QVBoxLayout(self)
+ toplayout.setSpacing(self.spacingHint())
+ toplayout.setMargin(self.marginHint())
+
+ contentbox = QHBox(self)
+ contentbox.setSpacing(self.spacingHint())
+ toplayout.addWidget(contentbox)
+ toplayout.setStretchFactor(contentbox,1)
+
+ label = QLabel(contentbox)
+ label.setPixmap(KGlobal.iconLoader().loadIcon("messagebox_warning", KIcon.NoGroup, KIcon.SizeMedium,
+ KIcon.DefaultState, None, True))
+ contentbox.setStretchFactor(label,0)
+
+ textbox = QVBox(contentbox)
+
+ textbox.setSpacing(self.spacingHint())
+ textbox.setMargin(self.marginHint())
+
+ self.usernamelabel = QLabel("",textbox)
+ textbox.setStretchFactor(self.usernamelabel,0)
+
+ # Remove directory checkbox.
+ self.deletedirectorycheckbox = QCheckBox(i18n("Delete home directory ()"),textbox)
+ textbox.setStretchFactor(self.deletedirectorycheckbox,0)
+
+ # Delete the User's private group.
+ self.deletegroupcheckbox = QCheckBox(i18n("Delete group ()"),textbox)
+ textbox.setStretchFactor(self.deletegroupcheckbox ,0)
+
+ # Buttons
+ buttonbox = QHBox(self)
+ toplayout.addWidget(buttonbox)
+
+ buttonbox.setSpacing(self.spacingHint())
+ toplayout.setStretchFactor(buttonbox,0)
+
+ spacer = QWidget(buttonbox)
+ buttonbox.setStretchFactor(spacer,1)
+
+ okbutton = QPushButton(i18n("OK"),buttonbox)
+ buttonbox.setStretchFactor(okbutton,0)
+ self.connect(okbutton,SIGNAL("clicked()"),self.slotOkClicked)
+
+ cancelbutton = QPushButton(i18n("Cancel"),buttonbox)
+ cancelbutton.setDefault(True)
+ buttonbox.setStretchFactor(cancelbutton,0)
+ self.connect(cancelbutton,SIGNAL("clicked()"),self.slotCancelClicked)
+
+ def deleteUser(self,userid):
+ # Setup the
+ userobj = self.admincontext.lookupUID(userid)
+ self.usernamelabel.setText(i18n("Are you sure want to delete user account '%1' (%2)?").arg(userobj.getUsername()).arg(userobj.getUID()) )
+ self.deletedirectorycheckbox.setChecked(False)
+ self.deletedirectorycheckbox.setText(i18n("Delete home directory (%1)").arg(userobj.getHomeDirectory()))
+ primarygroupobj = userobj.getPrimaryGroup()
+ primarygroupname = primarygroupobj.getGroupname()
+ self.deletegroupcheckbox.setText(i18n("Delete group '%1' (%2)").arg(primarygroupname).arg(primarygroupobj.getGID()))
+ self.deletegroupcheckbox.setChecked(len(primarygroupobj.getUsers())==1)
+ if self.exec_loop()==QDialog.Accepted:
+ self.admincontext.removeUser(userobj)
+ if self.deletedirectorycheckbox.isChecked():
+ self.admincontext.removeHomeDirectory(userobj)
+ # FIXME
+ #self.admincontext.removeMail(userobj)
+ if self.deletegroupcheckbox.isChecked():
+ self.admincontext.removeGroup(primarygroupobj)
+ self.admincontext.save()
+ return True
+ else:
+ return False
+
+ def slotOkClicked(self):
+ self.accept()
+
+ def slotCancelClicked(self):
+ self.reject()
+
+###########################################################################
+class OverwriteHomeDirectoryDialog(KDialog):
+ CANCEL = 0
+ OK_KEEP = 1
+ OK_REPLACE = 2
+
+ def __init__(self,parent):
+ KDialog.__init__(self,parent,"Create home directory",True,Qt.WType_Dialog)
+ self.setCaption(i18n("Create home directory"))
+ self.updatingGUI = True
+
+ toplayout = QVBoxLayout(self)
+ toplayout.setSpacing(self.spacingHint())
+ toplayout.setMargin(self.marginHint())
+
+ contentbox = QHBox(self)
+ contentbox.setSpacing(self.spacingHint())
+ toplayout.addWidget(contentbox)
+ toplayout.setStretchFactor(contentbox,1)
+
+ label = QLabel(contentbox)
+ label.setPixmap(KGlobal.iconLoader().loadIcon("messagebox_warning", KIcon.NoGroup, KIcon.SizeMedium,
+ KIcon.DefaultState, None, True))
+ contentbox.setStretchFactor(label,0)
+
+ textbox = QVBox(contentbox)
+
+ textbox.setSpacing(self.spacingHint())
+ textbox.setMargin(self.marginHint())
+
+ # "%dir was selected as the home directory for %user. This directory already exists. Shall I:."
+ self.toplabel = QLabel("",textbox)
+ textbox.setStretchFactor(self.toplabel,0)
+
+ self.radiogroup = QButtonGroup()
+ self.radiogroup.setRadioButtonExclusive(True)
+
+ # Use Existing home directory radio button.
+ self.usehomedirectoryradio = QRadioButton(i18n("Use the existing directory without changing it."),textbox)
+ textbox.setStretchFactor(self.usehomedirectoryradio,0)
+
+ # Replace home directory radio button
+ self.replacehomedirectoryradio = QRadioButton(i18n("Delete the directory and replace it with a new home directory."),textbox)
+ textbox.setStretchFactor(self.replacehomedirectoryradio ,0)
+
+ self.radiogroup.insert(self.usehomedirectoryradio,0)
+ self.radiogroup.insert(self.replacehomedirectoryradio,1)
+
+ # Buttons
+ buttonbox = QHBox(self)
+ toplayout.addWidget(buttonbox)
+
+ buttonbox.setSpacing(self.spacingHint())
+ toplayout.setStretchFactor(buttonbox,0)
+
+ spacer = QWidget(buttonbox)
+ buttonbox.setStretchFactor(spacer,1)
+
+ okbutton = QPushButton(i18n("OK"),buttonbox)
+ buttonbox.setStretchFactor(okbutton,0)
+ self.connect(okbutton,SIGNAL("clicked()"),self.slotOkClicked)
+
+ cancelbutton = QPushButton(i18n("Cancel"),buttonbox)
+ cancelbutton.setDefault(True)
+ buttonbox.setStretchFactor(cancelbutton,0)
+ self.connect(cancelbutton,SIGNAL("clicked()"),self.slotCancelClicked)
+
+ def do(self,userobj):
+ # Setup the
+ self.toplabel.setText(i18n("The directory '%1' was entered as the home directory for new user '%2'.\n This directory already exists.") \
+ .arg(userobj.getHomeDirectory()).arg(userobj.getUsername()) )
+ self.radiogroup.setButton(0)
+
+ if self.exec_loop()==QDialog.Accepted:
+ if self.radiogroup.selectedId()==0:
+ return OverwriteHomeDirectoryDialog.OK_KEEP
+ else:
+ return OverwriteHomeDirectoryDialog.OK_REPLACE
+ else:
+ return OverwriteHomeDirectoryDialog.CANCEL
+
+ def slotOkClicked(self):
+ self.accept()
+
+ def slotCancelClicked(self):
+ self.reject()
+
+###########################################################################
+class GroupEditDialog(KDialogBase):
+ def __init__(self,parent,admincontext):
+ KDialogBase.__init__(self,parent,None,True,i18n("Edit Group"),KDialogBase.Ok|KDialogBase.Cancel,
+ KDialogBase.Cancel)
+
+ self.admincontext = admincontext
+
+ topvbox = QVBox(self)
+ topvbox.setSpacing(self.spacingHint())
+ self.setMainWidget(topvbox)
+
+ detailspace = QWidget(topvbox)
+
+ # Info about the group.
+ editgrid = QGridLayout(detailspace,2,2)
+ editgrid.setSpacing(self.spacingHint())
+
+ label = QLabel(i18n("Group Name:"),detailspace)
+ editgrid.addWidget(label,0,0)
+ self.groupnamelabel = KLineEdit("",detailspace)
+ self.groupnamelabel.setReadOnly(True)
+ editgrid.addWidget(self.groupnamelabel,0,1)
+
+ label = QLabel(i18n("Group ID:"),detailspace)
+ editgrid.addWidget(label,1,0)
+ self.groupidlabel = KLineEdit("",detailspace)
+ self.groupidlabel.setReadOnly(True)
+ editgrid.addWidget(self.groupidlabel,1,1)
+
+ # Available Groups
+ tophbox = QHBox(topvbox)
+ tophbox.setSpacing(self.spacingHint())
+
+ hbox = tophbox
+
+ vbox = QVBox(hbox)
+ vbox.setSpacing(self.spacingHint())
+ hbox.setStretchFactor(vbox,1)
+ label = QLabel(i18n("Available Accounts"),vbox)
+ vbox.setStretchFactor(label,0)
+ self.availablelist = KListBox(vbox)
+ vbox.setStretchFactor(self.availablelist,1)
+
+ # ->, <- Buttons
+ vbox = QVBox(hbox)
+ vbox.setSpacing(self.spacingHint())
+ hbox.setStretchFactor(vbox,0)
+ spacer = QWidget(vbox);
+ vbox.setStretchFactor(spacer,1)
+ self.addbutton = KPushButton(i18n("Add ->"),vbox)
+ self.connect(self.addbutton,SIGNAL("clicked()"),self.slotAddClicked)
+ vbox.setStretchFactor(self.addbutton,0)
+ self.removebutton = KPushButton(i18n("<- Remove"),vbox)
+ self.connect(self.removebutton,SIGNAL("clicked()"),self.slotRemoveClicked)
+ vbox.setStretchFactor(self.removebutton,0)
+ spacer = QWidget(vbox);
+ vbox.setStretchFactor(spacer,1)
+
+ # Selected Groups
+ vbox = QVBox(hbox)
+ vbox.setSpacing(self.spacingHint())
+ hbox.setStretchFactor(vbox,1)
+ label = QLabel(i18n("Selected Accounts"),vbox)
+ vbox.setStretchFactor(label,0)
+ self.selectedlist = KListBox(vbox)
+ vbox.setStretchFactor(self.selectedlist,1)
+
+ #######################################################################
+ def showEditGroup(self,groupid):
+ self.groupid = groupid
+ self.newgroupmode = False
+
+ groupobj = self.admincontext.lookupGID(groupid)
+
+ self.groupnamelabel.setText(groupobj.getGroupname())
+ self.groupidlabel.setText(unicode(groupid))
+
+ availablemembers = [u.getUsername() for u in self.admincontext.getUsers()]
+ originalmembers = [u.getUsername() for u in groupobj.getUsers()]
+
+ self.__updateLists(availablemembers,originalmembers)
+
+ if self.exec_loop()==QDialog.Accepted:
+ newmembers = []
+ for i in range(self.selectedlist.count()):
+ newmembers.append(unicode(self.selectedlist.item(i).text()))
+
+ # Remove from the group object any unselected users.
+ for member in originalmembers:
+ if u not in newmembers:
+ self.admincontext.lookupUsername(member).removeFromGroup(groupobj)
+ # Put the additional members in the group
+ for member in newmembers:
+ self.admincontext.lookupUsername(member).addToGroup(groupobj)
+ self.admincontext.save()
+ return True
+ else:
+ return False
+
+ #######################################################################
+ def showNewGroup(self):
+ self.updatingGUI = True
+ self.newgroupmode = True
+
+ groupname = self.__fudgeNewGroupName(i18n("<Base string for creating new group names>","new_group"))
+
+ self.groupobj = self.admincontext.newGroup(True)
+ self.groupobj.setGroupname(groupname)
+
+ groupname = self.groupobj.getGroupname()
+ self.groupnamelabel.setText(groupname)
+ self.groupnamelabel.setReadOnly(False)
+ self.groupidlabel.setText(unicode(self.groupobj.getGID()))
+ self.groupidlabel.setReadOnly(False)
+
+ availablemembers = [u.getUsername() for u in self.admincontext.getUsers()]
+
+ self.__updateLists(availablemembers,[])
+
+ if self.exec_loop()==QDialog.Accepted:
+ self.groupobj.setGroupname(unicode(self.groupnamelabel.text()))
+ newgroupid = int(unicode(self.groupidlabel.text()))
+ self.groupobj.setGID(newgroupid)
+
+ newmembers = []
+ for i in range(self.selectedlist.count()):
+ newmembers.append(unicode(self.selectedlist.item(i).text()))
+
+ self.admincontext.addGroup(self.groupobj)
+
+ # Put the additional members in the group
+ for member in newmembers:
+ self.admincontext.lookupUsername(member).addToGroup(self.groupobj)
+ self.admincontext.save()
+
+ return newgroupid
+ else:
+ return None
+
+ #######################################################################
+ def slotAddClicked(self):
+ item = self.availablelist.selectedItem()
+ if item!=None:
+ self.selectedlist.insertItem(item.text())
+ self.availablelist.removeItem(self.availablelist.index(item))
+ self._selectFirstAvailable()
+ self._selectFirstSelected()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ #######################################################################
+ def slotRemoveClicked(self):
+ item = self.selectedlist.selectedItem()
+ if item!=None:
+ self.availablelist.insertItem(item.text())
+ self.selectedlist.removeItem(self.selectedlist.index(item))
+ self._selectFirstAvailable()
+ self._selectFirstSelected()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ #######################################################################
+ def __updateLists(self,grouplist,selectedlist):
+ self.selectedlist.clear()
+ for item in selectedlist:
+ self.selectedlist.insertItem(item)
+ self.selectedlist.sort()
+
+ self.availablelist.clear()
+ for item in grouplist:
+ if item not in selectedlist:
+ self.availablelist.insertItem(item)
+ self.availablelist.sort()
+
+ self._selectFirstAvailable()
+ self.addbutton.setDisabled(self.availablelist.selectedItem()==None)
+
+ self._selectFirstSelected()
+ self.removebutton.setDisabled(self.selectedlist.selectedItem()==None)
+
+ #######################################################################
+ def _selectFirstAvailable(self):
+ if self.availablelist.count()!=0:
+ if self.availablelist.selectedItem()==None:
+ self.availablelist.setSelected(0,True)
+
+ #######################################################################
+ def _selectFirstSelected(self):
+ if self.selectedlist.count()!=0:
+ if self.selectedlist.selectedItem()==None:
+ self.selectedlist.setSelected(0,True)
+
+ #######################################################################
+ def __fudgeNewGroupName(self,basename):
+ availablegroups = [g.getGroupname() for g in self.admincontext.getGroups()]
+ if basename not in availablegroups:
+ return basename
+ x = 1
+ while basename + u'_' + unicode(x) in availablegroups:
+ x += 1
+ return basename + u'_' + unicode(x)
+
+############################################################################
+# Factory function for KControl
+def create_userconfig(parent,name):
+ return UserConfigApp(parent, name)
+
+##########################################################################
+def MakeAboutData():
+ aboutdata = KAboutData("guidance", programname, version,
+ unicode(i18n("User and Group Configuration Tool")).encode(locale.getpreferredencoding()),
+ KAboutData.License_GPL, "Copyright (C) 2003-2007 Simon Edwards")
+ aboutdata.addAuthor("Simon Edwards", "Developer", "simon@simonzone.com", "http://www.simonzone.com/software/")
+ aboutdata.addAuthor("Sebastian Kügler", "Developer", "sebas@kde.org", "http://vizZzion.org")
+ return aboutdata
+
+if standalone:
+ aboutdata = MakeAboutData()
+
+ KCmdLineArgs.init(sys.argv,aboutdata)
+
+ kapp = KApplication()
+ userconfigapp = UserConfigApp()
+ userconfigapp.exec_loop()