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