Redbrick User management tool
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1470 lines
47 KiB

  1. #-----------------------------------------------------------------------------#
  2. # MODULE DESCRIPTION #
  3. #-----------------------------------------------------------------------------#
  4. """RedBrick User Database Module; contains RBUserDB class."""
  5. # System modules
  6. import crypt
  7. import fcntl
  8. import math
  9. import os
  10. import random
  11. import re
  12. import sys
  13. import time
  14. import types
  15. # 3rd party modules
  16. import ldap
  17. # RedBrick modules
  18. import rbconfig
  19. from rberror import *
  20. from rbopt import *
  21. from rbuser import *
  22. #-----------------------------------------------------------------------------#
  23. # DATA #
  24. #-----------------------------------------------------------------------------#
  25. __version__ = '$Revision: 1.10 $'
  26. __author__ = 'Cillian Sharkey'
  27. #-----------------------------------------------------------------------------#
  28. # CLASSES #
  29. #-----------------------------------------------------------------------------#
  30. class RBUserDB:
  31. """Class to interface with user database."""
  32. valid_shells = None
  33. backup_shells = None
  34. def __init__(self):
  35. """Create new RBUserDB object."""
  36. self.opt = RBOpt()
  37. self.ldap = None
  38. self.ldap_dcu = None
  39. #def connect(self, uri = rbconfig.ldap_uri, dn = rbconfig.ldap_root_dn, password = None, dcu_uri = rbconfig.ldap_dcu_uri):
  40. def connect(self, uri = rbconfig.ldap_uri, dn = rbconfig.ldap_root_dn, password = None, dcu_uri = rbconfig.ldap_dcu_uri,dcu_dn = rbconfig.ldap_dcu_rbdn, dcu_pw = None):
  41. """Connect to databases.
  42. Custom URI, DN and password may be given for RedBrick LDAP.
  43. Password if not given will be read from shared secret file set
  44. in rbconfig.
  45. Custom URI may be given for DCU LDAP.
  46. """
  47. if not password:
  48. try:
  49. fd = open(rbconfig.ldap_rootpw_file, 'r')
  50. password = fd.readline().rstrip()
  51. except IOError:
  52. raise RBFatalError("Unable to open LDAP root password file")
  53. fd.close()
  54. if not dcu_pw:
  55. try:
  56. fd = open(rbconfig.ldap_dcu_rbpw, 'r')
  57. dcu_pw = fd.readline().rstrip()
  58. except IOError:
  59. raise RBFatalError("Unable to open DCU AD root password file")
  60. fd.close()
  61. # Default protocol seems to be 2, set to 3.
  62. ldap.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
  63. # Connect to RedBrick LDAP.
  64. self.ldap = ldap.initialize(uri)
  65. self.ldap.simple_bind_s(dn, password)
  66. # Connect to DCU LDAP (anonymous bind).
  67. self.ldap_dcu = ldap.initialize(dcu_uri)
  68. # self.ldap_dcu.simple_bind_s('', '')
  69. self.ldap_dcu.simple_bind_s(dcu_dn,dcu_pw)
  70. def close(self):
  71. """Close database connections."""
  72. if self.ldap:
  73. self.ldap.unbind()
  74. if self.ldap_dcu:
  75. self.ldap_dcu.unbind()
  76. def setopt(self, opt):
  77. """Use given RBOpt object to retrieve options."""
  78. self.opt = opt
  79. #---------------------------------------------------------------------#
  80. # USER CHECKING AND INFORMATION RETRIEVAL METHODS #
  81. #---------------------------------------------------------------------#
  82. def check_userfree(self, uid):
  83. """Check if a username is free.
  84. If username is already used or is an LDAP group, an
  85. RBFatalError is raised. If the username is in the additional
  86. reserved LDAP tree, an RBWarningError is raised and checked if
  87. it is to be overridden.
  88. """
  89. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'uid=%s' % uid)
  90. if res:
  91. raise RBFatalError("Username '%s' is already taken by %s account (%s)" % (uid, res[0][1]['objectClass'][0], res[0][1]['cn'][0]))
  92. res = self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'cn=%s' % uid)
  93. if res:
  94. raise RBFatalError("Username '%s' is reserved (LDAP Group)" % uid)
  95. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, 'uid=%s' % uid)
  96. if res:
  97. self.rberror(RBWarningError("Username '%s' is reserved (%s)" % (uid, res[0][1]['description'][0])))
  98. def check_user_byname(self, uid):
  99. """Raise RBFatalError if given username does not exist in user
  100. database."""
  101. if not self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'uid=%s' % uid):
  102. raise RBFatalError("User '%s' does not exist" % uid)
  103. def check_user_byid(self, id):
  104. """Raise RBFatalError if given id does not belong to a user in
  105. user database."""
  106. if not self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'id=%s' % id):
  107. raise RBFatalError("User with id '%s' does not exist" % id)
  108. def check_group_byname(self, group):
  109. """Raise RBFatalError if given group does not exist in group
  110. database."""
  111. if not self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'cn=%s' % group):
  112. raise RBFatalError("Group '%s' does not exist" % group)
  113. def check_group_byid(self, gid):
  114. """Raise RBFatalError if given id does not belong to a group in
  115. group database."""
  116. if not self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'gidNumber=%s' % gid):
  117. raise RBFatalError("Group with id '%s' does not exist" % gid)
  118. #---------------------------------------------------------------------#
  119. # INFORMATION RETRIEVAL METHODS #
  120. #---------------------------------------------------------------------#
  121. # XXX still needed ?
  122. # def get_usertype_byname(self, uid):
  123. # """Return usertype for username in user database. Raise
  124. # RBFatalError if user does not exist."""
  125. #
  126. # res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'uid=%s' % usr.uid, ('objectClass',))
  127. # if res:
  128. # for i in res[0][1]['objectClass']:
  129. # if rbconfig.usertypes.has_key(i):
  130. # return i
  131. # else:
  132. # raise RBFatalError("Unknown usertype for user '%s'" % uid)
  133. # else:
  134. # raise RBFatalError("User '%s' does not exist" % uid)
  135. def get_user_byname(self, usr):
  136. """Populate RBUser object with data from user with given
  137. username in user database. Raise RBFatalError if user does not
  138. exist."""
  139. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'uid=%s' % usr.uid)
  140. if res:
  141. self.set_user(usr, res[0])
  142. else:
  143. raise RBFatalError("User '%s' does not exist" % usr.uid)
  144. def get_user_byid(self, usr):
  145. """Populate RBUser object with data from user with given id in
  146. user database. Raise RBFatalError if user does not exist."""
  147. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'id=%s' % usr.id)
  148. if res:
  149. self.set_user(usr, res[0])
  150. else:
  151. raise RBFatalError("User with id '%s' does not exist" % usr.id)
  152. def get_userinfo_new(self, usr, override = 0):
  153. """Checks if ID already belongs to an existing user and if so
  154. raises RBFatalError. Populates RBUser object with data for new
  155. user from DCU databases otherwise raises RBWarningError."""
  156. if usr.id != None:
  157. tmpusr = RBUser(id = usr.id)
  158. try:
  159. self.get_user_byid(tmpusr)
  160. except RBError:
  161. pass
  162. else:
  163. raise RBFatalError("Id '%s' is already registered to %s (%s)" % (usr.id, tmpusr.uid, tmpusr.cn))
  164. self.get_dcu_byid(usr, override)
  165. def get_userinfo_renew(self, usr, curusr = None, override = 0):
  166. """Merge RBUser object with current data from DCU & user
  167. databases. Set curusr if given to current data from user
  168. database."""
  169. # Load the user data currently in the database.
  170. #
  171. if not curusr:
  172. curusr = RBUser()
  173. curusr.uid = usr.uid
  174. curusr.id = usr.id
  175. if usr.uid:
  176. self.get_user_byname(curusr)
  177. else:
  178. self.get_user_byid(curusr)
  179. usr.usertype = usr.usertype or curusr.usertype
  180. usr.id = usr.id != None and usr.id or curusr.id
  181. self.check_renewal_usertype(usr.usertype)
  182. if usr.usertype in rbconfig.usertypes_dcu:
  183. # Load the dcu data using usertype and ID set in the given usr
  184. # or failing that from the current user database.
  185. #
  186. dcuusr = RBUser(uid = usr.uid, usertype = usr.usertype, id = usr.id)
  187. try:
  188. self.get_dcu_byid(dcuusr, override = 1)
  189. except RBError, e:
  190. self.rberror(e)
  191. # Any attributes not set in the given usr are taken from the
  192. # current dcu database or failing that, the current user
  193. # database.
  194. #
  195. # Exceptions to this are:
  196. #
  197. # - updatedby: caller must give this
  198. # - email: for associates as it may be changed from their DCU
  199. # address when they leave DCU so we don't want to
  200. # automatically overwrite it.
  201. # - usertype: if get_dcu_byid finds the dcu details, it'll set
  202. # the usertype as appropriate when override option is given,
  203. # so we automatically override this here too.
  204. #
  205. if usr.usertype == 'associat':
  206. dcuusr.altmail = None
  207. usr.merge(dcuusr, override = override)
  208. usr.merge(RBUser(curusr, updatedby = None))
  209. def get_userdefaults_new(self, usr):
  210. """Populate RBUser object with default values for a new user.
  211. Usertype should be provided, but a default of member will be
  212. assumed.
  213. """
  214. if not usr.usertype:
  215. usr.usertype = 'member'
  216. if usr.newbie == None:
  217. usr.newbie = 1
  218. if usr.yearsPaid == None and usr.usertype in rbconfig.usertypes_paying and usr.usertype not in ('committe', 'guest'):
  219. usr.yearsPaid = 1
  220. def get_userdefaults_renew(self, usr, override = 0):
  221. """Populate RBUser object with some reasonable default values
  222. for renewal user"""
  223. if usr.usertype in rbconfig.usertypes_paying:
  224. if usr.yearsPaid == None or usr.yearsPaid < 1:
  225. usr.yearsPaid = 1
  226. def get_dcu_byid(self, usr, override = 0):
  227. """Populates RBUser object with data for new user from
  228. appropriate DCU database for the given usertype. If usertype
  229. is not given, all DCU databases are tried and the usertype is
  230. determined from which database has the given ID. If no data for
  231. ID, raise RBWarningError."""
  232. # Just try all databases for a match regardless if
  233. # usertype was given or not. If usertype wasn't set or the
  234. # override option is given, set the usertype to the
  235. # corresponding database that had the ID.
  236. #
  237. usertype = None
  238. try:
  239. self.get_staff_byid(usr, override)
  240. except RBError:
  241. try:
  242. self.get_alumni_byid(usr, override)
  243. except RBError, e:
  244. try:
  245. self.get_student_byid(usr, override)
  246. except RBError, e:
  247. if usr.usertype not in ('associat', 'staff'):
  248. self.rberror(e)
  249. else:
  250. usertype = 'member'
  251. else:
  252. usertype = 'associat'
  253. else:
  254. usertype = 'staff'
  255. # XXX: this overrides committe people (typically back to member)
  256. # which probably shouldn't be done?
  257. if usertype and (override or not usr.usertype):
  258. usr.usertype = usertype
  259. return
  260. # Graduates now remain in the (currently student, but may
  261. # change) LDAP tree for their life long email accounts so try
  262. # to load in information for associates (but don't fail if we
  263. # can't).
  264. #
  265. # if usr.usertype in ('member', 'associat', 'committe'):
  266. # try:
  267. # self.get_student_byid(usr, override)
  268. # except RBError, e:
  269. # if usr.usertype != 'associat':
  270. # self.rberror(e)
  271. # # Not all staff may be in the LDAP tree, so don't fail if we
  272. # # can't get their information either.
  273. # #
  274. # elif usr.usertype == 'staff':
  275. # try:
  276. # self.get_staff_byid(usr, override)
  277. # except RBError:
  278. # pass
  279. def get_student_byid(self, usr, override = 0):
  280. """Populate RBUser object with data from user with given id in
  281. student database.
  282. By default will only populate RBUser attributes that have no
  283. value (None) unless override is enabled.
  284. Note that all students *should* be in the database, but only
  285. raise a RBWarningError if user does not exist.
  286. """
  287. res = self.ldap_dcu.search_s(rbconfig.ldap_dcu_students_tree, ldap.SCOPE_SUBTREE, 'employeeNumber=%s' % usr.id)
  288. if res:
  289. self.set_user_dcu(usr, res[0], override)
  290. self.set_user_dcu_student(usr, res[0], override)
  291. else:
  292. raise RBWarningError("Student id '%s' does not exist in database" % usr.id)
  293. def get_alumni_byid(self, usr, override = 0):
  294. """Populate RBUser object with data from user with given id in
  295. alumni database.
  296. By default will only populate RBUser attributes that have no
  297. value (None) unless override is enabled.
  298. Not all alumni will be in the database, so only raise a
  299. RBWarningError if user does not exist.
  300. """
  301. res = self.ldap_dcu.search_s(rbconfig.ldap_dcu_alumni_tree, ldap.SCOPE_SUBTREE, 'cn=%s' % usr.id)
  302. if res:
  303. self.set_user_dcu(usr, res[0], override)
  304. self.set_user_dcu_alumni(usr, res[0], override)
  305. else:
  306. raise RBWarningError("Alumni id '%s' does not exist in database" % usr.id)
  307. def get_staff_byid(self, usr, override = 0):
  308. """Populate RBUser object with data from user with given id in
  309. staff database.
  310. By default will only populate RBUser attributes that have no
  311. value (None) unless override is enabled.
  312. Not all staff are in the database, so only raise a
  313. RBWarningError if user does not exist.
  314. """
  315. # Staff ID is not consistently set. It will either be in the cn
  316. # or in the gecos, so try both.
  317. #
  318. res = self.ldap_dcu.search_s(rbconfig.ldap_dcu_staff_tree, ldap.SCOPE_SUBTREE, '(|(cn=%s)(gecos=*,*%s))' % (usr.id, usr.id))
  319. if res:
  320. self.set_user_dcu(usr, res[0], override)
  321. self.set_user_dcu_staff(usr, res[0], override)
  322. else:
  323. raise RBWarningError("Staff id '%s' does not exist in database" % usr.id)
  324. def get_dummyid(self, usr):
  325. """Set usr.id to unique 'dummy' DCU ID number."""
  326. raise RBFatalError('NOT YET IMPLEMENTED')
  327. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, '(&(id>=10000000)(id<20000000))"' % (usr.uid))
  328. if res:
  329. usr.id = int(results[0][1]['id'][0]) + 1
  330. else:
  331. usr.id = 10000000
  332. def get_gid_byname(self, group):
  333. """Get gid for given group name.
  334. Raise RBFatalError if given name does not belong to a group in
  335. group database."""
  336. res = self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'cn=%s' % group)
  337. if res:
  338. return int(res[0][1]['gidNumber'][0])
  339. else:
  340. raise RBFatalError("Group '%s' does not exist" % group)
  341. def get_group_byid(self, gid):
  342. """Get group name for given group ID.
  343. Raise RBFatalError if given id does not belong to a group in
  344. group database."""
  345. res = self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'gidNumber=%s' % gid)
  346. if res:
  347. return res[0][1]['cn'][0]
  348. else:
  349. raise RBFatalError("Group with id '%s' does not exist" % gid)
  350. def get_backup_shell(self, username):
  351. """Return shell for given user from previous year's LDAP tree
  352. or failing that, the default shell."""
  353. # XXX Use old passwd.backup file FTTB. Should use
  354. # ou=<prevyear>,ou=accounts tree instead.
  355. if self.backup_shells == None:
  356. self.backup_shells = {}
  357. fd = open(rbconfig.file_backup_passwd, 'r')
  358. for line in fd.readlines():
  359. pw = line.split(':')
  360. self.backup_shells[pw[0]] = pw[6].rstrip()
  361. fd.close()
  362. return self.backup_shells.get(username, rbconfig.shell_default)
  363. #---------------------------------------------------------------------#
  364. # USER DATA SYNTAX CHECK METHODS #
  365. #---------------------------------------------------------------------#
  366. def check_userdata(self, usr):
  367. """Verifies RBUser object's user data with the various
  368. check_*() methods. Raises RBError if any data is not valid."""
  369. self.check_username(usr.uid)
  370. self.check_usertype(usr.usertype)
  371. self.check_id(usr)
  372. self.check_email(usr)
  373. self.check_name(usr)
  374. self.check_years_paid(usr)
  375. self.check_updatedby(usr.updatedby)
  376. self.check_birthday(usr)
  377. def check_username(self, uid):
  378. """Raise RBFatalError if username is not valid."""
  379. if not uid:
  380. raise RBFatalError('Username must be given')
  381. if re.search(r'[^a-z0-9_.-]', uid):
  382. raise RBFatalError("Invalid characters in username")
  383. if len(uid) > rbconfig.maxlen_uname:
  384. raise RBFatalError("Username can not be longer than %d characters" % rbconfig.maxlen_uname)
  385. if re.search(r'^[^a-z0-9]', uid):
  386. raise RBFatalError("Username must begin with letter or number")
  387. def check_usertype(self, usertype):
  388. """Raise RBFatalError if usertype is not valid."""
  389. if not usertype:
  390. raise RBFatalError('Usertype must be given')
  391. if not rbconfig.usertypes.has_key(usertype):
  392. raise RBFatalError("Invalid usertype '%s'" % usertype)
  393. def check_convert_usertype(self, usertype):
  394. """Raise RBFatalError if conversion usertype is not valid."""
  395. if not (rbconfig.usertypes.has_key(usertype) or rbconfig.convert_usertypes.has_key(usertype)):
  396. raise RBFatalError("Invalid conversion usertype '%s'" % usertype)
  397. def check_renewal_usertype(self, usertype):
  398. """Raise RBFatalError if renewal usertype is not valid."""
  399. if not usertype in rbconfig.usertypes_paying:
  400. raise RBFatalError("Invalid renewal usertype '%s'" % usertype)
  401. def check_id(self, usr):
  402. """Raise RBFatalError if ID is not valid for usertypes that require one."""
  403. if usr.usertype in rbconfig.usertypes_dcu:
  404. if usr.id != None:
  405. if type(usr.id) != types.IntType:
  406. raise RBFatalError('ID must be an integer')
  407. if usr.id >= 100000000:
  408. raise RBFatalError("Invalid ID '%s'" % (usr.id))
  409. elif usr.usertype not in ('committe', 'guest'):
  410. raise RBFatalError('ID must be given')
  411. def check_years_paid(self, usr):
  412. """Raise RBFatalError if years_paid is not valid."""
  413. if usr.usertype in rbconfig.usertypes_paying:
  414. if usr.yearsPaid != None:
  415. if type(usr.yearsPaid) != types.IntType:
  416. raise RBFatalError('Years paid must be an integer')
  417. if usr.yearsPaid < -1:
  418. raise RBFatalError('Invalid number of years paid')
  419. elif usr.usertype not in ('committe', 'guest'):
  420. raise RBFatalError('Years paid must be given')
  421. def check_name(self, usr):
  422. """Raise RBFatalError if name is not valid."""
  423. if not usr.cn:
  424. raise RBFatalError('Name must be given')
  425. if usr.cn.find(':') >= 0:
  426. raise RBFatalError("No colon ':' characters allowed in name")
  427. def check_email(self, usr):
  428. """Raise RBError if email is not valid."""
  429. if not usr.altmail:
  430. raise RBFatalError('Email must be given')
  431. if not re.search(r'.+@.+', usr.altmail):
  432. raise RBFatalError("Invalid email address '%s'" % (usr.altmail))
  433. if usr.usertype in ('member', 'staff') and not re.search(r'.+@.*dcu\.ie', usr.altmail, re.I):
  434. self.rberror(RBWarningError("%s users should have a DCU email address" % (usr.usertype)))
  435. def check_updatedby(self, updatedby):
  436. """Raise RBFatalError if updatedby is not a valid username."""
  437. if not updatedby:
  438. raise RBFatalError('Updated by must be given')
  439. try:
  440. self.check_user_byname(updatedby)
  441. except RBError:
  442. raise RBFatalError("Updated by username '%s' is not valid" % updatedby)
  443. def check_birthday(self, usr):
  444. """Raise RBFatalError if the birthday is not valid, if set (it's optional)."""
  445. if usr.birthday:
  446. if not re.search(r'^\d{4}-\d{1,2}-\d{1,2}$', usr.birthday):
  447. raise RBFatalError('Birthday format must be YYYY-MM-DD')
  448. def check_disuser_period(self, usr):
  449. """Raise RBFatalError if the period of disuserment is not valid."""
  450. if usr.disuser_period and not re.search(r'^[-0-9a-zA-Z:"\'+ ]+$', usr.disuser_period):
  451. raise RBFatalError("Invalid characters in disuser period")
  452. def check_unpaid(self, usr):
  453. """Raise RBWarningError if the user is already paid up."""
  454. if usr.yearsPaid != None and usr.yearsPaid > 0:
  455. self.rberror(RBWarningError("User '%s' is already paid!" % usr.uid))
  456. #---------------------------------------------------------------------#
  457. # SINGLE USER METHODS #
  458. #---------------------------------------------------------------------#
  459. def add(self, usr):
  460. """Add new RBUser object to database."""
  461. # Note: it is safe to try all these functions without an
  462. # exception handler, as the functions will call rberror
  463. # internally for any RBWarningError exceptions that are raised.
  464. #
  465. self.check_userfree(usr)
  466. self.get_userinfo_new(usr)
  467. self.get_userdefaults_new(usr)
  468. self.check_userdata(usr)
  469. self.gen_accinfo(usr)
  470. self.set_updated(usr)
  471. fd, usr.uidNumber = self.uidNumber_getnext()
  472. if not usr.objectClass:
  473. usr.objectClass = [usr.usertype] + rbconfig.ldap_default_objectClass
  474. self.wrapper(self.ldap.add_s, self.uid2dn(usr.uid), self.usr2ldap_add(usr))
  475. self.uidNumber_savenext(fd, usr.uidNumber + 1)
  476. self.uidNumber_unlock(fd)
  477. def delete(self, usr):
  478. """Delete user from database."""
  479. self.check_user_byname(usr.uid)
  480. self.wrapper(self.ldap.delete_s, self.uid2dn(usr.uid))
  481. def renew(self, usr):
  482. """Renew and update RBUser object in database."""
  483. curusr = RBUser()
  484. self.get_userinfo_renew(usr, curusr)
  485. self.check_unpaid(curusr)
  486. self.get_userdefaults_renew(usr)
  487. self.check_userdata(usr)
  488. self.set_updated(usr)
  489. self.wrapper(self.ldap.modify_s, self.uid2dn(usr.uid), self.usr2ldap_renew(usr))
  490. def update(self, usr):
  491. """Update RBUser object in database."""
  492. self.get_user_byname(usr)
  493. self.check_updatedby(usr.updatedby)
  494. self.check_userdata(usr)
  495. self.set_updated(usr)
  496. self.wrapper(self.ldap.modify_s, self.uid2dn(usr.uid), self.usr2ldap_update(usr))
  497. def rename(self, usr, newusr):
  498. """Rename a user.
  499. Renamed additonal attributes (homeDirectory, updated,
  500. updatedby) are set in newusr.
  501. Requires: usr.uid, usr.updatedby, newusr.uid.
  502. """
  503. self.check_username(usr.uid)
  504. self.check_username(newusr.uid)
  505. self.get_user_byname(usr)
  506. self.check_userfree(newusr.uid)
  507. self.check_updatedby(usr.updatedby)
  508. # Rename DN first.
  509. #
  510. self.wrapper(self.ldap.rename_s, self.uid2dn(usr.uid), 'uid=%s' % newusr.uid)
  511. # Rename homedir and update the updated* attributes.
  512. #
  513. self.set_updated(newusr)
  514. newusr.homeDirectory = rbconfig.gen_homedir(newusr.uid, usr.usertype)
  515. newusr.merge(usr)
  516. self.wrapper(self.ldap.modify_s, self.uid2dn(newusr.uid), self.usr2ldap_rename(newusr))
  517. def convert(self, usr, newusr):
  518. """Convert a user to a different usertype."""
  519. self.get_user_byname(usr)
  520. self.check_convert_usertype(newusr.usertype)
  521. self.check_updatedby(usr.updatedby)
  522. # If usertype is one of the pseudo usertypes, change the
  523. # usertype to 'committe' for the database conversion.
  524. #
  525. if rbconfig.convert_usertypes.has_key(newusr.usertype):
  526. newusr.usertype = 'committe'
  527. raise RBFatalError('NOT IMPLEMENTED YET')
  528. # Rename homedir, replace old usertype with new usertype in
  529. # objectClass and update gidNumber to new usertype.
  530. #
  531. newusr.homeDirectory = rbconfig.gen_homedir(usr.uid, newusr.usertype)
  532. newusr.gidNumber = self.get_gid_byname(newusr.usertype)
  533. newusr.objectClass = []
  534. for i in usr.objectClass:
  535. if i == usr.usertype:
  536. newusr.objectClass.append(newusr.usertype)
  537. else:
  538. newusr.objectClass.append(i)
  539. newusr.merge(usr)
  540. self.wrapper(self.ldap.modify_s, self.uid2dn(usr.uid), self.usr2ldap_convert(newusr))
  541. def set_passwd(self, usr):
  542. """Set password for given user from the plaintext password in usr.passwd."""
  543. usr.userPassword = self.userPassword(usr.passwd)
  544. self.wrapper(self.ldap.modify_s, self.uid2dn(usr.uid), ((ldap.MOD_REPLACE, 'userPassword', usr.userPassword),))
  545. def set_shell(self, usr):
  546. """Set shell for given user."""
  547. self.wrapper(self.ldap.modify_s, self.uid2dn(usr.uid), ((ldap.MOD_REPLACE, 'loginShell', usr.loginShell),))
  548. def reset_shell(self, usr):
  549. """Reset shell for given user."""
  550. tmpusr = RBUser(uid = usr.uid)
  551. self.get_user_byname(tmpusr)
  552. if self.valid_shell(tmpusr.loginShell):
  553. return 0
  554. usr.loginShell = self.get_backup_shell(usr.uid)
  555. self.set_shell(usr)
  556. return 1
  557. #---------------------------------------------------------------------#
  558. # SINGLE USER INFORMATION METHODS #
  559. #---------------------------------------------------------------------#
  560. def show(self, usr):
  561. """Show RBUser object information on standard output."""
  562. for i in usr.attr_list_all:
  563. if getattr(usr, i) != None:
  564. print "%13s: %s" % (i, getattr(usr, i))
  565. def info(self, usr):
  566. """Show passwordless RBUser object information on standard output."""
  567. for i in usr.attr_list_info:
  568. if getattr(usr, i) != None:
  569. print "%13s: %s" % (i, getattr(usr, i))
  570. def show_diff(self, usr, oldusr):
  571. """
  572. Show RBUser object information on standard output.
  573. Show any attributes in usr which differ in value from those in
  574. oldusr.
  575. """
  576. for i in 'uid', 'usertype', 'newbie', 'cn', 'altmail', 'id', 'course', 'year', 'yearsPaid':
  577. v = getattr(usr, i)
  578. if v != None:
  579. ov = getattr(oldusr, i, None)
  580. print "%15s: %s" % (ov != v and "(NEW) " + i or i, v)
  581. if ov != v:
  582. print "%15s: %s" % ("(OLD) " + i, getattr(oldusr, i))
  583. #---------------------------------------------------------------------#
  584. # BATCH INFORMATION METHODS #
  585. #---------------------------------------------------------------------#
  586. #-------------------------#
  587. # METHODS RETURNING LISTS #
  588. #-------------------------#
  589. def list_pre_sync(self):
  590. """Return dictionary of all users for useradm pre_sync() dump."""
  591. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'objectClass=posixAccount', ('uid', 'homeDirectory', 'objectClass'))
  592. tmp = {}
  593. for dn, data in res:
  594. for i in data['objectClass']:
  595. if rbconfig.usertypes.has_key(i):
  596. break
  597. else:
  598. raise RBFatalError("Unknown usertype for user '%s'" % data['uid'][0])
  599. tmp[data['uid'][0]] = {
  600. 'homeDirectory': data['homeDirectory'][0],
  601. 'usertype': i
  602. }
  603. return tmp
  604. def list_users(self):
  605. """Return list of all usernames."""
  606. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'objectClass=posixAccount', ('uid',))
  607. return [data['uid'][0] for dn, data in res]
  608. def list_paid_newbies(self):
  609. """Return list of all paid newbie usernames."""
  610. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, '(&(yearsPaid>=1)(newbie=TRUE))', ('uid',))
  611. return [data['uid'][0] for dn, data in res]
  612. def list_paid_non_newbies(self):
  613. """Return list of all paid renewal (non-newbie) usernames."""
  614. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, '(&(yearsPaid>=1)(newbie=FALSE))', ('uid',))
  615. return [data['uid'][0] for dn, data in res]
  616. def list_non_newbies(self):
  617. """Return list of all non newbie usernames."""
  618. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'newbie=FALSE', ('uid',))
  619. return [data['uid'][0] for dn, data in res]
  620. def list_newbies(self):
  621. """Return list of all newbie usernames."""
  622. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'newbie=TRUE', ('uid',))
  623. return [data['uid'][0] for dn, data in res]
  624. def list_groups(self):
  625. """Return list of all groups."""
  626. res = self.ldap.search_s(rbconfig.ldap_group_tree, ldap.SCOPE_ONELEVEL, 'objectClass=posixGroup', ('cn',))
  627. return [data['cn'][0] for dn, data in res]
  628. def list_reserved(self):
  629. """Return list of all reserved entries."""
  630. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, 'objectClass=reserved', ('uid',))
  631. return [data['uid'][0] for dn, data in res]
  632. def list_reserved_static(self):
  633. """Return list of all static reserved names."""
  634. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, '(&(objectClass=reserved)(flag=static))', ('uid',))
  635. return [data['uid'][0] for dn, data in res]
  636. def list_reserved_dynamic(self):
  637. """Return list of all dynamic reserved names."""
  638. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, '(&(objectClass=reserved)(!(flag=static)))', ('uid',))
  639. return [data['uid'][0] for dn, data in res]
  640. def list_reserved_all(self):
  641. """Return list of all usernames that are taken or reserved.
  642. This includes all account usernames, all reserved usernames and
  643. all groupnames."""
  644. return self.list_users() + self.list_reserved() + self.list_groups()
  645. def list_unpaid(self):
  646. """Return list of all non-renewed users."""
  647. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'yearsPaid<=0', ('uid',))
  648. return [data['uid'][0] for dn, data in res]
  649. def list_unpaid_normal(self):
  650. """Return list of all normal non-renewed users."""
  651. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'yearsPaid=0', ('uid',))
  652. return [data['uid'][0] for dn, data in res]
  653. def list_unpaid_grace(self):
  654. """Return list of all grace non-renewed users."""
  655. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'yearsPaid<=-1', ('uid',))
  656. return [data['uid'][0] for dn, data in res]
  657. def list_unpaid_reset(self):
  658. """Return list of all non-renewed users with reset shells (i.e. not expired)."""
  659. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, '(&(yearsPaid<=0)(!(loginShell=%s)))' % rbconfig.shell_expired, ('uid',))
  660. return [data['uid'][0] for dn, data in res]
  661. #--------------------------------#
  662. # METHODS RETURNING DICTIONARIES #
  663. #--------------------------------#
  664. def dict_reserved_desc(self):
  665. """Return dictionary of all reserved entries with their
  666. description."""
  667. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, 'objectClass=reserved', ('uid', 'description'))
  668. tmp = {}
  669. for dn, data in res:
  670. tmp[data['uid'][0]] = data['description'][0]
  671. return tmp
  672. def dict_reserved_static(self):
  673. """Return dictionary of all static reserved entries with their
  674. description."""
  675. res = self.ldap.search_s(rbconfig.ldap_reserved_tree, ldap.SCOPE_ONELEVEL, '(&(objectClass=reserved)(flag=static))', ('uid', 'description'))
  676. tmp = {}
  677. for dn, data in res:
  678. tmp[data['uid'][0]] = data['description'][0]
  679. return tmp
  680. #----------------------------------#
  681. # METHODS RETURNING SEARCH RESULTS #
  682. #----------------------------------#
  683. def search_users_byusername(self, uid):
  684. """Search user database by username and return results
  685. ((username, usertype, id, name, course, year, email), ...)"""
  686. raise RBFatalError("NOT IMLEMENTED YET")
  687. self.cur.execute('SELECT username, usertype, id, name, course, year, email FROM users WHERE username LIKE %s', ('%%%s%%' % uid,))
  688. return self.cur.fetchall()
  689. def search_users_byid(self, id):
  690. """Search user database by id and return results
  691. ((username, id, name, course, year), ...)"""
  692. raise RBFatalError("NOT IMLEMENTED YET")
  693. return self.search_users('id LIKE', id)
  694. def search_users_byname(self, name):
  695. """Search user database by name and return results as per
  696. search_users_byid()."""
  697. raise RBFatalError("NOT IMLEMENTED YET")
  698. return self.search_users('name ILIKE', name)
  699. def search_users(self, where, var):
  700. """Performs actual user database search with given where clause
  701. and data."""
  702. raise RBFatalError("NOT IMLEMENTED YET")
  703. self.cur.execute('SELECT username, usertype, id, name, course, year, email FROM users WHERE ' + where + ' %s', ('%%%s%%' % var,))
  704. return self.cur.fetchall()
  705. def search_dcu_byid(self, id):
  706. """Search user & DCU databases by id and return results
  707. ((username, id, name, course, year), ...)"""
  708. raise RBFatalError("NOT IMLEMENTED YET")
  709. return self.search_dcu('s.id LIKE', id)
  710. def search_dcu_byname(self, name):
  711. """Search user & DCU databases by name and return results as
  712. per search_dcu_byid"""
  713. raise RBFatalError("NOT IMLEMENTED YET")
  714. return self.search_dcu('s.name ILIKE', name)
  715. def search_dcu(self, where, var):
  716. """Performs actual DCU database search with given where clause
  717. and data."""
  718. raise RBFatalError("NOT IMLEMENTED YET")
  719. var = '%%%s%%' % var
  720. self.cur.execute('SELECT u.username, u.usertype, s.id, s.name, s.course, s.year, s.email FROM students s LEFT JOIN users u USING (id) WHERE ' + where + ' %s', (var,))
  721. res = self.cur.fetchall()
  722. self.cur.execute('SELECT u.username, u.usertype, s.id, s.name, s.email FROM staff s LEFT JOIN users u USING (id) WHERE ' + where + '%s', (var,))
  723. return [(username, usertype, id, name, None, None, email) for username, usertype, id, name, email in self.cur.fetchall()] + res
  724. #---------------------------------------------------------------------#
  725. # BATCH METHODS #
  726. #---------------------------------------------------------------------#
  727. def newyear(self):
  728. """Prepare database for start of new academic year.
  729. This involves the following: creating a backup of the current
  730. users table for the previous academic year for archival
  731. purposes, reducing all paying users subscription by one year
  732. and setting the newbie field to false for all users.
  733. """
  734. raise RBFatalError("NOT IMLEMENTED YET")
  735. year = time.localtime()[0] - 1
  736. self.execute('CREATE TABLE users%d AS SELECT * FROM users', (year,))
  737. self.execute('CREATE INDEX users%d_username_key ON users%d (username)', (year, year))
  738. self.execute('CREATE INDEX users%d_id_key ON users%d (id)', (year, year))
  739. self.execute('UPDATE users SET years_paid = years_paid - 1 WHERE years_paid IS NOT NULL')
  740. self.execute("UPDATE users SET newbie = 'f'")
  741. self.dbh.commit()
  742. #---------------------------------------------------------------------#
  743. # MISCELLANEOUS METHODS #
  744. #---------------------------------------------------------------------#
  745. def stats(self):
  746. """Print database statistics on standard output."""
  747. usertypes = {}
  748. categories = (
  749. 'paid', 'unpaid', 'nonpay', 'newbie',
  750. 'signed_paid', 'signed_unpaid', 'signed_nonpay', 'signed_newbie',
  751. 'nosign_paid', 'nosign_unpaid', 'nosign_nonpay', 'nosign_newbie',
  752. 'TOTAL'
  753. )
  754. for k in rbconfig.usertypes:
  755. usertypes[k] = dict([(c,0) for c in categories])
  756. for uid in self.list_users():
  757. usr = RBUser(uid=uid)
  758. self.get_user_byname(usr)
  759. usertypes[usr.usertype]['TOTAL'] += 1
  760. signed = not self.opt.dbonly and os.path.exists('%s/%s' % (rbconfig.dir_signaway_state, uid))
  761. pay = usr.yearsPaid == None and 'nonpay' or usr.yearsPaid > 0 and 'paid' or 'unpaid'
  762. usertypes[usr.usertype][pay] += 1
  763. usertypes[usr.usertype]['%s_%s' % (not signed and 'nosign' or 'signed', pay)] += 1
  764. if usr.newbie:
  765. usertypes[usr.usertype]['newbie'] += 1
  766. usertypes[usr.usertype]['%s_newbie' % (not signed and 'nosign' or 'signed')] += 1
  767. ordered_usertypes = list(rbconfig.usertypes_list) + [i for i in rbconfig.usertypes if i not in rbconfig.usertypes_list]
  768. # Print out table.
  769. #
  770. print " " * 9,
  771. for c in categories:
  772. if len(c) > 6:
  773. print "%7s" % c[:6],
  774. else:
  775. print " " * 7,
  776. print
  777. print " " * 9,
  778. for c in categories:
  779. if len(c) > 6:
  780. print "%7.6s" % c[-(len(c)-6):],
  781. else:
  782. print "%7s" % c,
  783. print
  784. print " " * 9,
  785. for i in range(len(categories)):
  786. print ' ', '=' * 5,
  787. print
  788. # Work out category totals.
  789. #
  790. category_totals = dict([(c,0) for c in categories])
  791. for u in ordered_usertypes:
  792. print "%9s" % u,
  793. ut = usertypes[u]
  794. for c in categories:
  795. print "%7d" % ut[c],
  796. category_totals[c] += ut[c]
  797. print
  798. print " " * 9,
  799. for i in range(len(categories)):
  800. print ' ', '=' * 5,
  801. print
  802. print '%9s' % 'ALL',
  803. for c in categories:
  804. print "%7d" % category_totals[c],
  805. print
  806. print
  807. t = 0
  808. for u in 'member', 'committe', 'staff':
  809. t += usertypes[u]['paid']
  810. print "Total paid members, committee & staff:", t
  811. print "Quorum (rounded-up square root of above):", math.ceil(math.sqrt(t))
  812. print "'Active' users (paid and non-paying signed-in users):", category_totals['signed_paid'] + category_totals['signed_nonpay']
  813. print "%d of %d newbies signed-in (%d%%)" % (category_totals['signed_newbie'], category_totals['newbie'], 100.0 * category_totals['signed_newbie'] / category_totals['newbie'])
  814. print
  815. def crypt(self, password):
  816. """Return crypted DES password."""
  817. saltchars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  818. return crypt.crypt(password, saltchars[random.randrange(len(saltchars))] + saltchars[random.randrange(len(saltchars))])
  819. def userPassword(self, password):
  820. """Return string suitable for LDAP userPassword attribute based on given plaintext password."""
  821. if password:
  822. return "{CRYPT}" + self.crypt(password)
  823. # XXX: is this correct way to disable password?
  824. return "{CRYPT}*"
  825. def uid2dn(self, uid):
  826. """Return full Distinguished Name (DN) for given username."""
  827. return "uid=%s,%s" % (uid, rbconfig.ldap_accounts_tree)
  828. def uidNumber_findmax(self):
  829. """Return highest uidNumber found in LDAP accounts tree.
  830. This is only used to create the uidNumber file, the
  831. uidNumber_readnext() function should be used for getting the
  832. next available uidNumber."""
  833. res = self.ldap.search_s(rbconfig.ldap_accounts_tree, ldap.SCOPE_ONELEVEL, 'objectClass=posixAccount', ('uidNumber',))
  834. maxuid = -1
  835. for i in res:
  836. tmp = int(i[1]['uidNumber'][0])
  837. if tmp > maxuid:
  838. maxuid = tmp
  839. return maxuid
  840. def uidNumber_getnext(self):
  841. """Get the next available uidNumber for adding a new user.
  842. Locks uidNumber file, reads number. Returns (file descriptor,
  843. uidNumber). uidNumber_savenext() must be called once the
  844. uidNumber is used successfully."""
  845. fd = os.open(rbconfig.file_uidNumber, os.O_RDWR)
  846. retries = 0
  847. while 1:
  848. try:
  849. fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
  850. except IOError:
  851. retries += 1
  852. if retries == 20:
  853. raise RBFatalError('Could not lock uidNumber.txt file after 20 attempts. Please try again!')
  854. time.sleep(0.5)
  855. else:
  856. break
  857. n = int(os.read(fd, 32))
  858. return fd, n
  859. def uidNumber_savenext(self, fd, uidNumber):
  860. """Save next uidNumber.
  861. Writes uidNumber to file descriptor fd, which must be the one
  862. returned by uidNumber_getnext(). Does not write anything if in
  863. test mode."""
  864. if not self.opt.test:
  865. os.lseek(fd, 0, 0)
  866. os.write(fd, '%s\n' % uidNumber)
  867. os.fdatasync(fd)
  868. def uidNumber_unlock(self, fd):
  869. """Unlock uidNumber text file.
  870. This must be called after the last call to uidNumber_save() so
  871. that other processes can now obtain a lock on this file.
  872. The file will be unlocked after process termination though."""
  873. os.close(fd)
  874. def valid_shell(self, shell):
  875. """Check if given shell is valid by checking against /etc/shells."""
  876. if not shell:
  877. return 0
  878. if self.valid_shells == None:
  879. self.valid_shells = {}
  880. re_shell = re.compile(r'^([^\s#]+)')
  881. fd = open(rbconfig.file_shells, 'r')
  882. for line in fd.readlines():
  883. res = re_shell.search(line)
  884. if res:
  885. self.valid_shells[res.group(1)] = 1
  886. fd.close()
  887. return self.valid_shells.has_key(shell)
  888. #--------------------------------------------------------------------#
  889. # INTERNAL METHODS #
  890. #--------------------------------------------------------------------#
  891. def usr2ldap_add(self, usr):
  892. """Return a list of (type, attribute) pairs for given user.
  893. This list is used in LDAP add queries."""
  894. tmp = [
  895. ('uid', usr.uid),
  896. ('objectClass', usr.objectClass),
  897. ('newbie', usr.newbie and 'TRUE' or 'FALSE'),
  898. ('cn', usr.cn),
  899. ('altmail', usr.altmail),
  900. ('updatedby', usr.updatedby),
  901. ('updated', usr.updated),
  902. ('createdby', usr.updatedby),
  903. ('created', usr.created),
  904. ('uidNumber', str(usr.uidNumber)),
  905. ('gidNumber', str(usr.gidNumber)),
  906. ('gecos', usr.gecos),
  907. ('loginShell', usr.loginShell),
  908. ('homeDirectory', usr.homeDirectory),
  909. ('userPassword', usr.userPassword),
  910. ('host', usr.host)
  911. ]
  912. if usr.id != None:
  913. tmp.append(('id', str(usr.id)))
  914. if usr.course:
  915. tmp.append(('course', usr.course))
  916. if usr.year != None:
  917. tmp.append(('year', usr.year))
  918. if usr.yearsPaid != None:
  919. tmp.append(('yearsPaid', str(usr.yearsPaid)))
  920. if usr.birthday:
  921. tmp.append(('birthday', usr.birthday))
  922. return tmp
  923. def usr2ldap_renew(self, usr):
  924. """Return a list of (type, attribute) pairs for given user.
  925. This list is used in LDAP modify queries for renewing."""
  926. tmp = [
  927. (ldap.MOD_REPLACE, 'newbie', usr.newbie and 'TRUE' or 'FALSE'),
  928. (ldap.MOD_REPLACE, 'cn', usr.cn),
  929. (ldap.MOD_REPLACE, 'altmail', usr.altmail),
  930. (ldap.MOD_REPLACE, 'updatedby', usr.updatedby),
  931. (ldap.MOD_REPLACE, 'updated', usr.updated),
  932. ]
  933. if usr.id != None:
  934. tmp.append((ldap.MOD_REPLACE, 'id', str(usr.id)))
  935. if usr.course:
  936. tmp.append((ldap.MOD_REPLACE, 'course', usr.course))
  937. if usr.year != None:
  938. tmp.append((ldap.MOD_REPLACE, 'year', usr.year))
  939. if usr.yearsPaid != None:
  940. tmp.append((ldap.MOD_REPLACE, 'yearsPaid', str(usr.yearsPaid)))
  941. if usr.birthday:
  942. tmp.append((ldap.MOD_REPLACE, 'birthday', usr.birthday))
  943. return tmp
  944. def usr2ldap_update(self, usr):
  945. """Return a list of (type, attribute) pairs for given user.
  946. This list is used in LDAP modify queries for updating."""
  947. tmp = [
  948. (ldap.MOD_REPLACE, 'newbie', usr.newbie and 'TRUE' or 'FALSE'),
  949. (ldap.MOD_REPLACE, 'cn', usr.cn),
  950. (ldap.MOD_REPLACE, 'altmail', usr.altmail),
  951. (ldap.MOD_REPLACE, 'updatedby', usr.updatedby),
  952. (ldap.MOD_REPLACE, 'updated', usr.updated)
  953. ]
  954. if usr.id != None:
  955. tmp.append((ldap.MOD_REPLACE, 'id', str(usr.id)))
  956. if usr.course:
  957. tmp.append((ldap.MOD_REPLACE, 'course', usr.course))
  958. if usr.year != None:
  959. tmp.append((ldap.MOD_REPLACE, 'year', usr.year))
  960. if usr.yearsPaid != None:
  961. tmp.append((ldap.MOD_REPLACE, 'yearsPaid', str(usr.yearsPaid)))
  962. if usr.birthday:
  963. tmp.append((ldap.MOD_REPLACE, 'birthday', usr.birthday))
  964. return tmp
  965. def usr2ldap_rename(self, usr):
  966. """Return a list of (type, attribute) pairs for given user.
  967. This list is used in LDAP modify queries for renaming."""
  968. return (
  969. (ldap.MOD_REPLACE, 'homeDirectory', usr.homeDirectory),
  970. (ldap.MOD_REPLACE, 'updatedby', usr.updatedby),
  971. (ldap.MOD_REPLACE, 'updated', usr.updated)
  972. )
  973. def usr2ldap_convert(self, usr):
  974. """Return a list of (type, attribute) pairs for given user.
  975. This list is used in LDAP modify queries for converting."""
  976. return (
  977. (ldap.MOD_REPLACE, 'objectClass', usr.objectClass),
  978. (ldap.MOD_REPLACE, 'gidNumber', str(usr.gidNumber)),
  979. (ldap.MOD_REPLACE, 'homeDirectory', usr.homeDirectory),
  980. (ldap.MOD_REPLACE, 'updatedby', usr.updatedby),
  981. (ldap.MOD_REPLACE, 'updated', usr.updated)
  982. )
  983. def gen_accinfo(self, usr):
  984. if not usr.userPassword:
  985. usr.userPassword = self.userPassword(usr.passwd)
  986. if not usr.homeDirectory:
  987. usr.homeDirectory = rbconfig.gen_homedir(usr.uid, usr.usertype)
  988. if usr.gidNumber == None:
  989. usr.gidNumber = self.get_gid_byname(usr.usertype)
  990. if not usr.gecos:
  991. # Hide a user's identity for paying accounts.
  992. #
  993. if usr.usertype in rbconfig.usertypes_paying:
  994. usr.gecos = usr.uid
  995. else:
  996. usr.gecos = usr.cn
  997. if not usr.loginShell:
  998. usr.loginShell = rbconfig.shell_default
  999. if not usr.host:
  1000. usr.host = rbconfig.ldap_default_hosts
  1001. def set_updated(self, usr):
  1002. """Set updated in given usr to current local time.
  1003. Also sets created and createdby if not set in given usr."""
  1004. usr.updated = time.strftime('%Y-%m-%d %H:%M:%S')
  1005. if not usr.created:
  1006. usr.created = usr.updated
  1007. usr.createdby = usr.updatedby
  1008. def set_user(self, usr, res):
  1009. """Populate RBUser object with information from LDAP query.
  1010. By default will only populate RBUser attributes that have no
  1011. value (None).
  1012. """
  1013. if not usr.usertype:
  1014. for i in res[1]['objectClass']:
  1015. if rbconfig.usertypes.has_key(i):
  1016. usr.usertype = i
  1017. break
  1018. else:
  1019. raise RBFatalError("Unknown usertype for user '%s'" % usr.uid)
  1020. for k, v in res[1].items():
  1021. if getattr(usr, k) == None:
  1022. if k == 'newbie':
  1023. usr.newbie = v[0] == 'TRUE'
  1024. elif k not in RBUser.attr_list_value:
  1025. setattr(usr, k, v[0])
  1026. else:
  1027. setattr(usr, k, v)
  1028. # LDAP returns everything as strings, so booleans and integers
  1029. # need to be converted:
  1030. #
  1031. if usr.id:
  1032. usr.id = int(usr.id)
  1033. if usr.yearsPaid:
  1034. usr.yearsPaid = int(usr.yearsPaid)
  1035. usr.uidNumber = int(usr.uidNumber)
  1036. usr.gidNumber = int(usr.gidNumber)
  1037. def set_user_dcu(self, usr, res, override = 0):
  1038. """Populate RBUser object with common information from DCU LDAP query.
  1039. By default will only populate RBUser attributes that have no
  1040. value (None) unless override is enabled.
  1041. """
  1042. # Construct their full name from first name ('givenName')
  1043. # followed by their surname ('sn') or failing that, from their
  1044. # gecos up to the comma.
  1045. #
  1046. if override or usr.cn == None:
  1047. if res[1].get('givenName') and res[1].get('sn'):
  1048. usr.cn = '%s %s' % (res[1]['givenName'][0], res[1]['sn'][0])
  1049. elif res[1].get('gecos'):
  1050. usr.cn = res[1].get('gecos')[:res[1].get('gecos').find(',')]
  1051. if override or usr.altmail == None:
  1052. usr.altmail = res[1]['mail'][0]
  1053. def set_user_dcu_student(self, usr, res, override = 0):
  1054. """Populate RBUser object with student information from DCU
  1055. LDAP query."""
  1056. # Extract course & year from 'l' attribute if set. Assumes last
  1057. # character is the year (1, 2, 3, 4, X, O, C, etc.) and the
  1058. # rest is the course name. Uppercase course & year for
  1059. # consistency.
  1060. #
  1061. if res[1].get('physicalDeliveryOfficeName'):
  1062. if override or usr.course == None:
  1063. usr.course = res[1]['physicalDeliveryOfficeName'][0][:-1].upper()
  1064. if override or usr.year == None:
  1065. usr.year = res[1]['physicalDeliveryOfficeName'][0][-1].upper()
  1066. def set_user_dcu_staff(self, usr, res, override = 0):
  1067. """Populate RBUser object with staff information from DCU
  1068. LDAP query."""
  1069. # Set course to department name from 'l' attribute if set.
  1070. #
  1071. if res[1].get('l'):
  1072. if override or usr.course == None:
  1073. usr.course = res[1]['l'][0]
  1074. def set_user_dcu_alumni(self, usr, res, override = 0):
  1075. """Populate RBUser object with alumni information from DCU
  1076. LDAP query."""
  1077. # Extract course & year from 'l' attribute if set. Assumes
  1078. # syntax of [a-zA-Z]+[0-9]+ i.e. course code followed by year
  1079. # of graduation. Uppercase course for consistency.
  1080. #
  1081. if res[1].get('l'):
  1082. tmp = res[1].get('l')[0]
  1083. for i in range(len(tmp)):
  1084. if tmp[i].isdigit():
  1085. if override or usr.year == None:
  1086. usr.year = tmp[i:]
  1087. if override or usr.course == None:
  1088. usr.course = tmp[:i].upper()
  1089. break
  1090. else:
  1091. if override or usr.course == None:
  1092. usr.course = tmp.upper()
  1093. def wrapper(self, function, *keywords, **arguments):
  1094. """Wrapper method for executing other functions.
  1095. If test mode is set, print function name and arguments.
  1096. Otherwise call function with arguments.
  1097. """
  1098. if self.opt.test:
  1099. sys.stderr.write("TEST: %s(" % function.__name__)
  1100. for i in keywords:
  1101. sys.stderr.write("%s, " % (i,))
  1102. for k, v in arguments.items():
  1103. sys.stderr.write("%s = %s, " % (k, v))
  1104. sys.stderr.write(")\n")
  1105. else:
  1106. return function(*keywords, **arguments)
  1107. def execute(self, sql, params = None):
  1108. """Wrapper method for executing given SQL query."""
  1109. if params == None:
  1110. params = ()
  1111. if self.opt.test:
  1112. print >> sys.stderr, "TEST:", (sql % params)
  1113. else:
  1114. self.cur.execute(sql, params)
  1115. #--------------------------------------------------------------------#
  1116. # ERROR HANDLING #
  1117. #--------------------------------------------------------------------#
  1118. def rberror(self, e):
  1119. """Handle RBError exceptions.
  1120. If e is an RBWarningError and the override option is set,
  1121. ignore the exception and return. Otherwise, raise the exception
  1122. again.
  1123. """
  1124. if self.opt.override and isinstance(e, RBWarningError):
  1125. return
  1126. # If we reach here it's either a FATAL error or there was no
  1127. # override for a WARNING error, so raise it again to let the
  1128. # caller handle it.
  1129. #
  1130. raise e