rcSecurityMan.py

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

Generated on Thu Apr 27 20:52:43 2006 for LICOS L02-01-00 by doxygen 1.4.6-NO