Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | Related Pages

rcSecurityMan.py

00001 #!/usr/local/bin/python
00002 #
00003 #                               Copyright 2003
00004 #                                     by
00005 #                        The Board of Trustees of the
00006 #                     Leland Stanford Junior University.
00007 #                            All rights reserved.
00008 #
00009 
00010 __facility__ = "Online"
00011 __abstract__ = "Security Manager for RunControl"
00012 __author__   = "S. Tuvi <STuvi@SLAC.Stanford.edu>"
00013 __date__     = ("$Date: 2005/01/10 21:18:16 $").split(' ')[1]
00014 __version__  = "$Revision: 2.5 $"
00015 __release__  = "$Name: R04-12-00 $"
00016 __credits__  = "SLAC"
00017 
00018 import LATTE.copyright_SLAC
00019 
00020 import os
00021 from struct       import calcsize, pack, unpack
00022 #from binascii     import hexlify
00023 #import md5
00024 import sys
00025 import logging as log
00026 from ConfigParser import ConfigParser
00027 import sha, random, base64
00028 
00029 class rcSecurityMan(object):
00030   """Security Manager class that contains
00031   user authentication and rights management routines.
00032   """
00033   def __init__(self, secureDir, users):
00034     self.__secureDir = secureDir
00035     self.__users = users
00036     self.__passwords = os.path.join(self.__secureDir, "passwords")
00037     self.__security = os.path.join(self.__secureDir, "security.cfg")
00038     self.__fmt = "40s 256s 32s 40s h"
00039     self.__roles = {}
00040     self.__permissions = {}
00041 
00042   def readPermissions(self):
00043     """Read permissions from the security.cfg file
00044     and update user objects.
00045     """
00046     if os.access(self.__passwords,os.O_RDONLY):
00047       permParser = ConfigParser()
00048       permParser.read(self.__security)
00049       pUsers = permParser.options("users")
00050       for loginId in pUsers:
00051         user = self.__users.getUserByLoginId(loginId)
00052         if user is not None:
00053           roles = permParser.get("users", loginId).split(", ")
00054           for role in roles:
00055             user.addRole(role)
00056             if role not in self.__roles:
00057               self.__roles[role] = []
00058             if role == 'administrator': continue
00059             if not permParser.has_option("roles", role):
00060               log.warn("No permissions defined for role %s" % role)
00061             else:
00062               permissions = permParser.get("roles", role).split(", ")
00063               for permission in permissions:
00064                 if not permParser.has_option("permissions", permission):
00065                   log.warn("Permission %s is not in the security database" % permission)
00066                 else:
00067                   permDesc = permParser.get("permissions", permission)
00068                   user.addPermission(permission)
00069                   if permission not in self.__roles[role]:
00070                     self.__roles[role].append(permission)
00071                     if permission not in self.__permissions:
00072                       self.__permissions[permission] = permDesc
00073                     else:
00074                       log.warn("Permission %s has already been defined." % permission)
00075 
00076         # Add the roles and permissions which do not belong to any user
00077         for role in permParser.options("roles"):
00078           if role not in self.__roles:
00079             self.__roles[role] = []
00080             permissions = permParser.get("roles", role).split(", ")
00081             for permission in permissions:
00082               if permission in permParser.options("permissions"):
00083                 self.__roles[role].append(permission)
00084         for permission in permParser.options("permissions"):
00085           if permission not in self.__permissions:
00086             self.__permissions[permission] = permParser.get("permissions", permission)
00087 
00088 
00089   def registerPermission(self, role, permission, permDesc):
00090     """Method available to be called by a user script that
00091     allows the script to register a permission given a role
00092     and update any user permissions who belong in that role
00093 
00094     \param role       Role name for the \a permission to be included.
00095     \param permission Permission name.
00096     \param permDesc   Permission description
00097     """
00098     if self.roleHasPermission(role, permission):
00099       log.warn("Permission %s already exists in role %s" % (permission, role))
00100     else:
00101       if permission not in self.__permissions:
00102         self.__permissions[permission] = permDesc
00103       for user in self.__users:
00104         if role in user.getRoles():
00105           if permission not in user.getPermissions():
00106             user.addPermission(permission)
00107 
00108   def checkPermission(self, user, permission):
00109     """Checks if the user has been granted the specified permission
00110 
00111     \param user       rcUser object instance
00112     \param permission Permission name
00113 
00114     \return True:  If the \a user is granted the \a permission
00115             False: Otherwise
00116     """
00117     return (permission in user.getPermissions())
00118 
00119   def roleHasPermission(self, role, permission):
00120     """Check if \a permission exists in \a role.
00121 
00122     \param role Role name
00123     \param permission Permission name
00124 
00125     \return True:  If \a role has \a permission.
00126             False: otherwise.
00127     """
00128     return (permission in self.__roles[role])
00129 
00130   def getPermissions(self, role=None):
00131     """Returns the list of all permissions defined.
00132     If \a role has been specified then returns
00133     all permissions defined for that role.
00134 
00135     \param role Role name
00136 
00137     \return List of sll permissions or permissions for the specified role
00138     """
00139     if role is not None:
00140       return self.__roles[role]
00141     else:
00142       return self.__permissions.keys()
00143 
00144   def getPermissionDescription(self, permission):
00145     """Returns the description for the specified permission.
00146     If permission does not exist, returns None.
00147 
00148     \param permission Permission name
00149 
00150     \return Permission description string
00151     """
00152     if permission in self.__permissions:
00153       return self.__permissions[permission]
00154 
00155   def getRoles(self):
00156     """Returns the list of all roles defined.
00157 
00158     \return List of all roles.
00159     """
00160     return self.__roles.keys()
00161 
00162   def authenticateUser(self, loginId, password):
00163     """Authenticate the user based on the given login id and password.
00164 
00165     \param loginId  User's login id
00166     \param password User's password
00167 
00168     \return -1: User authentication failed, invalid user id or password
00169             -2: Password database is not accessible
00170              0: Access granted
00171     """
00172     openFlag = os.O_RDONLY
00173     if sys.platform =='win32':
00174       openFlag|=os.O_BINARY
00175     if os.access(self.__passwords,os.O_RDONLY):
00176       fd = os.open(self.__passwords,openFlag)
00177     else:
00178       log.debug("Password database is not accessible.")
00179       return -2
00180 
00181     #md5sum = md5.new()
00182     user_name = "Nothing"
00183     user_home = "Nothing"
00184     user_login = loginId
00185     user_password = password
00186     #md5sum.update(user_password)
00187     #user_passhash = hexlify(md5sum.digest())
00188     user_passhash="Nothing"
00189     user_attempts = 0
00190     user_sizeof = calcsize(self.__fmt)
00191     user_data = pack(self.__fmt,user_name,user_home,user_login,user_passhash,user_attempts)
00192     while len(user_data) == user_sizeof:
00193       user_data = os.read(fd,user_sizeof)
00194       if len(user_data) == user_sizeof:
00195         (u_name,u_home,u_login,u_passhash,u_attempts) = unpack(self.__fmt,user_data)
00196         #print "<%s><%s><%s><%s>" % (u_login, user_login, u_passhash, user_passhash)
00197         if u_login.strip('\0 ') == user_login.strip() and self.__check(u_passhash, user_password):
00198           log.debug("Access Granted to user with the login id=%s" % loginId)
00199           os.close(fd)
00200           return 0
00201     log.debug("User authentication failed, loginId=%s" % loginId)
00202     os.close(fd)
00203     return -1
00204 
00205 
00206   def changePassword(self, loginId, password, userName, oldPassword=""):
00207     """Change an existing user's password.
00208 
00209     \param loginId     User's login id
00210     \param password    The new password for the user
00211     \param userName    User's full name
00212     \param oldPassword User's old password (optional).
00213                        If this parameter is not specified the old password
00214                        is not verified. This is useful when the password
00215                        needs to be reset due to a forgotten password.
00216 
00217     \return -1: Invalid login or password
00218             -2: Password database is not accessible
00219             -3: Error accessing the password database
00220              0: Password changed successfully
00221     """
00222 
00223     try:
00224       # Need to use low-level file I/O here since we are going to use lseek
00225       if os.access(self.__passwords,os.O_RDWR):
00226         fd = os.open(self.__passwords,os.O_BINARY|os.O_RDWR)
00227       else:
00228         log.debug("Password database is not accessible.")
00229         return -2
00230     except:
00231       log.exception("Error accessing the password database")
00232       return -3
00233 
00234 
00235     #md5sum = md5.new()
00236     user_name = userName
00237     user_home = "Nothing"
00238     user_login = loginId
00239     user_password = oldPassword
00240     #md5sum.update(user_password)
00241     #user_passhash = hexlify(md5sum.digest())
00242     user_passhash = "Nothing"
00243     user_attempts = 0
00244     user_sizeof = calcsize(self.__fmt)
00245     user_data = pack(self.__fmt,user_name,user_home,user_login,user_passhash,user_attempts)
00246     while len(user_data) == user_sizeof:
00247       user_data = os.read(fd, user_sizeof)
00248       if len(user_data) == user_sizeof:
00249         (u_name,u_home,u_login,u_passhash,u_attempts) = unpack(self.__fmt,user_data)
00250         if u_login.strip('\0 ') == user_login.strip() and (oldPassword == "" or self.__check(u_passhash, user_password)):
00251           new_password = password
00252           #newmd5sum = md5.new(new_password)
00253           #new_passhash = hexlify(newmd5sum.digest())
00254           new_passhash = self.__create(new_password)
00255           user_info = pack(self.__fmt,user_name,u_home,u_login,new_passhash,u_attempts)
00256           os.lseek(fd,-user_sizeof,1)
00257           os.write(fd,user_info)
00258           os.close(fd)
00259           log.debug("Password for user %s changed" % loginId)
00260           return 0
00261     os.close(fd)
00262     log.debug("Invalid login or password trying to change the password for user %s" % loginId)
00263     return -1
00264 
00265   def addPassword(self, loginId, password, userName):
00266     """Add a new user to the password database
00267 
00268     \param loginId  User's login id
00269     \param password The new password for the user
00270     \param userName User's full name
00271 
00272     \return -3: Error accessing the password database
00273             -2: User already exists in password database
00274             -1: Invalid login or password
00275              0: Added user to the password database
00276     """
00277     try:
00278       if os.access(self.__passwords,os.O_RDWR):
00279         fd = os.open(self.__passwords,os.O_BINARY|os.O_RDWR)
00280         newfile = 0
00281       else:
00282         fd = os.open(self.__passwords,os.O_BINARY|os.O_RDWR|os.O_CREAT)
00283         newfile = 1
00284     except:
00285       log.exception("Error accessing the password database")
00286       return -3
00287     #md5sum = md5.new()
00288     user_name = userName
00289     user_home = "Nothing"
00290     user_login = loginId
00291     user_password = password
00292     #md5sum.update(user_password)
00293     #user_passhash = hexlify(md5sum.digest())
00294     user_passhash = self.__create(user_password)
00295     user_attempts = 0
00296     user_sizeof = calcsize(self.__fmt)
00297     user_info = pack(self.__fmt,user_name,user_home,user_login,user_passhash,user_attempts)
00298     user_data = user_info
00299     if newfile:
00300       log.debug("Added user %s to the password database" % loginId)
00301       os.write(fd,user_info)
00302       os.close(fd)
00303       return 0
00304     while len(user_data) == user_sizeof:
00305       user_data = os.read(fd,user_sizeof)
00306       if len(user_data) == user_sizeof:
00307         (u_name,u_home,u_login,u_passhash,u_attempts) = unpack(self.__fmt,user_data)
00308         if u_login.strip('\0 ') == user_login.strip():
00309           log.debug("User %s already exists in password database." % loginId)
00310           os.close(fd)
00311           return -2
00312     log.debug("Added user %s to the password database" % loginId)
00313     os.write(fd,user_info)
00314     os.close(fd)
00315     return 0
00316 
00317   def checkPassword(self, loginId):
00318     """Checks if a user already has a password
00319 
00320     \param loginId  User's login id
00321 
00322     \return -1: Password does not exist
00323              0: Password exists
00324     """
00325     if os.access(self.__passwords,os.O_RDONLY):
00326       fd = os.open(self.__passwords,os.O_BINARY|os.O_RDONLY)
00327     else:
00328       return -1
00329     #md5sum = md5.new()
00330     user_name = "Nothing"
00331     user_home = "Nothing"
00332     user_login = loginId
00333     user_password = "Nothing"
00334     user_passhash = "Nothing"
00335     user_attempts = 0
00336     user_sizeof = calcsize(self.__fmt)
00337     user_info = pack(self.__fmt,user_name,user_home,user_login,user_passhash,user_attempts)
00338     user_data = user_info
00339     while len(user_data) == user_sizeof:
00340       user_data = os.read(fd,user_sizeof)
00341       if len(user_data) == user_sizeof:
00342         (u_name,u_home,u_login,u_passhash,u_attempts) = unpack(self.__fmt,user_data)
00343         if u_login.strip('\0 ') == user_login.strip():
00344           os.close(fd)
00345           return 0
00346     os.close(fd)
00347     return -1
00348 
00349 
00350   def deleteUser(self, loginId):
00351     """Deletes the user based on the given login id and password.
00352 
00353     \param loginId  User's login id
00354     \param password User's password
00355 
00356     \return -1: User authentication failed, invalid user id or password
00357             -2: Password database is not accessible
00358              0: User deleted
00359     """
00360     if os.access(self.__passwords,os.O_RDWR):
00361       fd = os.open(self.__passwords,os.O_BINARY|os.O_RDWR)
00362     else:
00363       log.debug("Password database is not accessible.")
00364       return -2
00365 
00366     #md5sum = md5.new()
00367     (fmode, fino, fdev, fnlink, fuid, fgid, fsize, fatime, fmtime, fctime) = os.fstat(fd)
00368     user_name = "Nothing"
00369     user_home = "Nothing"
00370     user_login = loginId
00371     #user_password = password
00372     #md5sum.update(user_password)
00373     #user_passhash = hexlify(md5sum.digest())
00374     user_passhash="Nothing"
00375     user_attempts = 0
00376     user_sizeof = calcsize(self.__fmt)
00377     user_data = pack(self.__fmt,user_name,user_home,user_login,user_passhash,user_attempts)
00378     while len(user_data) == user_sizeof:
00379       user_data = os.read(fd,user_sizeof)
00380       if len(user_data) == user_sizeof:
00381         (u_name,u_home,u_login,u_passhash,u_attempts) = unpack(self.__fmt,user_data)
00382         #print "<%s><%s><%s><%s>" % (u_login, user_login, u_passhash, user_passhash)
00383         if u_login.strip('\0 ') == user_login.strip(): # and self.__check(u_passhash, user_password):
00384           log.debug("Access Granted to user with the login id=%s" % loginId)
00385           curpos = os.lseek(fd,0,1)
00386           if curpos == fsize:
00387             os.close(fd)
00388             f = open(self.__passwords, "ab+")
00389             f.truncate(fsize - user_sizeof)
00390             f.close()
00391             log.debug("User %s deleted" % loginId)
00392             return 0
00393           buf = os.read(fd,fsize - curpos)
00394           os.lseek(fd,curpos - user_sizeof,0)
00395           os.write(fd,buf)
00396           os.close(fd)
00397           f = open(self.__passwords,"ab+")
00398           f.truncate(fsize - user_sizeof)
00399           f.close()
00400           log.debug("User %s deleted" % loginId)
00401           return 0
00402     log.debug("User authentication failed, loginId=%s" % loginId)
00403     os.close(fd)
00404     return -1
00405 
00406 
00407   ## private:
00408 
00409   # Password encryption/verification routines
00410   def __makesalt(self):
00411       l = [ chr(random.randint(0,255)) for i in range(4) ]
00412       return ''.join(l)
00413 
00414   def __create(self, password):
00415       salt = self.__makesalt()
00416       text = salt + password
00417       pwdHash = sha.new(text).digest()
00418       data = salt + pwdHash
00419       return base64.encodestring(data)
00420 
00421   def __check(self, data, password):
00422       data = base64.decodestring(data)
00423       salt, pwdHash = data[:4], data[4:]
00424       pwdHash2 = sha.new(salt + password).digest()
00425       return pwdHash2 == pwdHash
00426 

Generated on Fri Jul 21 13:26:31 2006 for LATTE R04-12-00 by doxygen 1.4.3