00001
00002
00003
00004
00005
00006
00007
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
00024
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
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
00194 user_name = "Nothing"
00195 user_home = "Nothing"
00196 user_login = loginId
00197 user_password = password
00198
00199
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
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
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
00248 user_name = userName
00249 user_home = "Nothing"
00250 user_login = loginId
00251 user_password = oldPassword
00252
00253
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
00265
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
00300 user_name = userName
00301 user_home = "Nothing"
00302 user_login = loginId
00303 user_password = password
00304
00305
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
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
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
00384
00385
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
00395 if u_login.strip('\0 ') == user_login.strip():
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
00420
00421
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