Package backend :: Package server :: Module rhnUser
[hide private]
[frames] | no frames]

Source Code for Module backend.server.rhnUser

  1  # 
  2  # Copyright (c) 2008--2017 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15  # 
 16  # Stuff for handling Certificates and Servers 
 17  # 
 18   
 19  import re 
 20  import crypt 
 21   
 22  # Global Modules 
 23  from rhn.UserDictCase import UserDictCase 
 24  from spacewalk.common.rhnLog import log_debug, log_error 
 25  from spacewalk.common.rhnConfig import CFG 
 26  from spacewalk.common.rhnException import rhnFault, rhnException 
 27  from spacewalk.common.rhnTranslate import _ 
 28   
 29  import rhnSQL 
 30  import rhnSession 
 31   
 32   
33 -class User:
34 35 """ Main User class """ 36
37 - def __init__(self, username, password):
38 # compatibilty with the rest of the code 39 self.username = username 40 41 # placeholders for the table schemas 42 # web_contact 43 self.contact = rhnSQL.Row("web_contact", "id") 44 self.contact["login"] = username 45 self.contact["password"] = password 46 # web_customer 47 self.customer = rhnSQL.Row("web_customer", "id") 48 self.customer["name"] = username 49 self.customer["password"] = password 50 # web_user_personal_info 51 self.__init_info() 52 # web_user_contact_permission 53 self.__init_perms() 54 # web_user_site_info 55 self.__init_site() 56 self._session = None
57
58 - def __init_info(self):
59 """ init web_user_personal_info """ 60 # web_user_personal_info 61 self.info = rhnSQL.Row("web_user_personal_info", 62 "web_user_id") 63 self.info['first_names'] = "Valued" 64 self.info['last_name'] = "Customer" 65 self.info['prefix'] = "Mr."
66
67 - def __init_perms(self):
68 """ init web_user_contact_permission """ 69 # web_user_contact_permission 70 self.perms = rhnSQL.Row("web_user_contact_permission", 71 "web_user_id") 72 self.perms["email"] = "Y" 73 self.perms["mail"] = "Y" 74 self.perms["call"] = "Y" 75 self.perms["fax"] = "Y"
76
77 - def __init_site(self):
78 """ init web_user_site_info """ 79 # web_user_site_info 80 self.site = rhnSQL.Row("web_user_site_info", "id") 81 self.site['city'] = "." 82 self.site['address1'] = "." 83 self.site['country'] = "US" 84 self.site['type'] = "M" 85 self.site['notes'] = "Entry created by Spacewalk registration process"
86
87 - def check_password(self, password, allow_read_only=False):
88 """ simple check for a password that might become more complex sometime """ 89 if not allow_read_only and is_user_read_only(self.contact["login"]): 90 raise rhnFault(702) 91 good_pwd = str(self.contact["password"]) 92 if CFG.pam_auth_service: 93 # a PAM service is defined 94 # We have to check the user's rhnUserInfo.use_pam_authentication 95 # XXX Should we create yet another __init_blah function? 96 # since it's the first time we had to lool at rhnUserInfo, 97 # I'll assume it's not something to happen very frequently, 98 # so I'll use a query for now 99 # - misa 100 # 101 h = rhnSQL.prepare(""" 102 select ui.use_pam_authentication 103 from web_contact w, rhnUserInfo ui 104 where w.login_uc = UPPER(:login) 105 and w.id = ui.user_id""") 106 h.execute(login=self.contact["login"]) 107 data = h.fetchone_dict() 108 if not data: 109 # This should not happen 110 raise rhnException("No entry found for user %s" % 111 self.contact["login"]) 112 if data['use_pam_authentication'] == 'Y': 113 # use PAM 114 import rhnAuthPAM 115 return rhnAuthPAM.check_password(self.contact["login"], 116 password, CFG.pam_auth_service) 117 # If the entry in rhnUserInfo is 'N', perform regular authentication 118 ret = check_password(password, good_pwd) 119 if ret and CFG.encrypted_passwords and self.contact['password'].find('$1$') == 0: 120 # If successfully authenticated and the current password is 121 # MD5 encoded, convert the password to SHA-256 and save it in the DB. 122 self.contact['password'] = encrypt_password(password) 123 self.contact.save() 124 rhnSQL.commit() 125 return ret
126
127 - def set_org_id(self, org_id):
128 if not org_id: 129 raise rhnException("Invalid org_id requested for user", org_id) 130 self.contact["org_id"] = int(org_id) 131 self.customer.load(int(org_id))
132
133 - def getid(self):
134 if not self.contact.has_key("id"): 135 userid = rhnSQL.Sequence("web_contact_id_seq")() 136 self.contact.data["id"] = userid # kind of illegal, but hey! 137 else: 138 userid = self.contact["id"] 139 return userid
140
141 - def set_contact_perm(self, name, value):
142 """ handling of contact permissions """ 143 if not name: 144 return -1 145 n = name.lower() 146 v = 'N' 147 if value: 148 v = 'Y' 149 if n == "contact_phone": 150 self.perms["call"] = v 151 elif n == "contact_mail": 152 self.perms["mail"] = v 153 elif n == "contact_email": 154 self.perms["email"] = v 155 elif n == "contact_fax": 156 self.perms["fax"] = v 157 return 0
158
159 - def set_info(self, name, value):
160 """ set a certain value for the userinfo field. This is BUTT ugly. """ 161 log_debug(3, name, value) 162 # translation from what the client send us to real names of the fields 163 # in the tables. 164 mapping = { 165 "first_name": "first_names", 166 "position": "title", 167 "title": "prefix" 168 } 169 if not name: 170 return -1 171 name = name.lower() 172 if type(value) == type(""): 173 value = value.strip() 174 # We have to watch over carefully for different field names 175 # being sent from rhn_register 176 changed = 0 177 178 # translation 179 if name in list(mapping.keys()): 180 name = mapping[name] 181 # Some fields can not have null string values 182 if name in ["first_names", "last_name", "prefix", # personal_info 183 "address1", "city", "country"]: # site_info 184 # we require something of it 185 if len(str(value)) == 0: 186 return -1 187 # fields in personal_info (and some in site) 188 if name in ["last_name", "first_names", 189 "company", "phone", "fax", "email", "title"]: 190 self.info[name] = value[:128] 191 changed = 1 192 elif name == "prefix": 193 values = ["Mr.", "Mrs.", "Ms.", "Dr.", "Hr.", "Sr.", " "] 194 # Now populate a dictinary of valid values 195 valids = UserDictCase() 196 for v in values: # initialize from good values, with and w/o the dot 197 valids[v] = v 198 valids[v[:-1]] = v 199 # commonly encountered values 200 valids["Miss"] = "Miss" 201 valids["Herr"] = "Hr." 202 valids["Sig."] = "Sr." 203 valids["Sir"] = "Mr." 204 # Now check it out 205 if valids.has_key(value): 206 self.info["prefix"] = valids[value] 207 changed = 1 208 else: 209 log_error("Unknown prefix value `%s'. Assumed `Mr.' instead" 210 % value) 211 self.info["prefix"] = "Mr." 212 changed = 1 213 214 # fields in site 215 if name in ["phone", "fax", "zip"]: 216 self.site[name] = value[:32] 217 changed = 1 218 elif name in ["city", "country", "alt_first_names", "alt_last_name", 219 "address1", "address2", "email", 220 "last_name", "first_names"]: 221 if name == "last_name": 222 self.site["alt_last_name"] = value 223 changed = 1 224 elif name == "first_names": 225 self.site["alt_first_names"] = value 226 changed = 1 227 else: 228 self.site[name] = value[:128] 229 changed = 1 230 elif name in ["state"]: # stupid people put stupid things in here too 231 self.site[name] = value[:60] 232 changed = 1 233 if not changed: 234 log_error("SET_INFO: Unknown info `%s' = `%s'" % (name, value)) 235 return 0
236
237 - def get_roles(self):
238 user_id = self.getid() 239 240 h = rhnSQL.prepare(""" 241 select ugt.label as role 242 from rhnUserGroup ug, 243 rhnUserGroupType ugt, 244 rhnUserGroupMembers ugm 245 where ugm.user_id = :user_id 246 and ugm.user_group_id = ug.id 247 and ug.group_type = ugt.id 248 """) 249 h.execute(user_id=user_id) 250 return [x['role'] for x in h.fetchall_dict() or []]
251
252 - def reload(self, user_id):
253 """ Reload the current data from the SQL database using the given id """ 254 log_debug(3, user_id) 255 256 # If we can not load these we have a fatal condition 257 if not self.contact.load(user_id): 258 raise rhnException("Could not find contact record for id", user_id) 259 if not self.customer.load(self.contact["org_id"]): 260 raise rhnException("Could not find org record", 261 "user_id = %s" % user_id, 262 "org_id = %s" % self.contact["org_id"]) 263 # These other ones are non fatal because we can create dummy records 264 if not self.info.load(user_id): 265 self.__init_info() 266 if not self.perms.load(user_id): 267 self.__init_perms() 268 # The site info is trickier, we need to find it first 269 if not self.site.load_sql("web_user_id = :userid and type = 'M'", 270 {"userid": user_id}): 271 self.__init_site() 272 # Fix the username 273 self.username = self.contact['login'] 274 return 0
275
276 - def create_session(self):
277 if self._session: 278 return self._session 279 280 self.session = rhnSession.generate(web_user_id=self.getid()) 281 return self.session
282 283
284 -def auth_username_password(username, password):
285 # hrm. it'd be nice to move importlib.userAuth stuff here 286 user = search(username) 287 288 if not user: 289 raise rhnFault(2, _("Invalid username/password combination")) 290 291 if not user.check_password(password): 292 raise rhnFault(2, _("Invalid username/password combination")) 293 294 return user
295 296
297 -def session_reload(session_string):
298 log_debug(4, session_string) 299 session = rhnSession.load(session_string) 300 web_user_id = session.uid 301 if not web_user_id: 302 raise rhnSession.InvalidSessionError("No user associated with session") 303 304 u = User("", "") 305 ret = u.reload(web_user_id) 306 if ret != 0: 307 # Something horked 308 raise rhnFault(10) 309 return u
310 311
312 -def get_user_id(username):
313 """ search for an userid """ 314 username = str(username) 315 h = rhnSQL.prepare(""" 316 select w.id from web_contact w 317 where w.login_uc = upper(:username) 318 """) 319 h.execute(username=username) 320 data = h.fetchone_dict() 321 if data: 322 return data["id"] 323 return None
324 325
326 -def search(user):
327 """ search the database for a user """ 328 log_debug(3, user) 329 userid = get_user_id(user) 330 if not userid or is_user_disabled(user): 331 # no user found or is disabled 332 return None 333 ret = User(user, "") 334 if not ret.reload(userid) == 0: 335 # something horked during reloading entry from database 336 # we can not realy say that the entry does not exist... 337 raise rhnFault(10) 338 return ret
339 340
341 -def is_user_disabled(user):
342 log_debug(3, user) 343 username = str(user) 344 h = rhnSQL.prepare(""" 345 select 1 from rhnWebContactDisabled 346 where login_uc = upper(:username) 347 """) 348 h.execute(username=username) 349 row = h.fetchone_dict() 350 if row: 351 return 1 352 return 0
353 354
355 -def is_user_read_only(user):
356 log_debug(3, user) 357 username = str(user) 358 h = rhnSQL.prepare(""" 359 select 1 from web_contact 360 where login_uc = upper(:username) 361 and read_only = 'Y' 362 """) 363 h.execute(username=username) 364 row = h.fetchone_dict() 365 if row: 366 return 1 367 return 0
368 369
370 -def reserve_user(username, password):
371 """ create a reservation record """ 372 return __reserve_user_db(username, password)
373 374
375 -def __reserve_user_db(user, password):
376 encrypted_password = CFG.encrypted_passwords 377 log_debug(3, user, CFG.disallow_user_creation, encrypted_password, CFG.pam_auth_service) 378 user = str(user) 379 h = rhnSQL.prepare(""" 380 select w.id, w.password, w.org_id, ui.use_pam_authentication 381 from web_contact w, rhnUserInfo ui 382 where w.login_uc = upper(:p1) 383 and w.id = ui.user_id 384 """) 385 h.execute(p1=user) 386 data = h.fetchone_dict() 387 if data and data["id"]: 388 # contact exists, check password 389 if data['use_pam_authentication'] == 'Y' and CFG.pam_auth_service: 390 # We use PAM for authentication 391 import rhnAuthPAM 392 if rhnAuthPAM.check_password(user, password, CFG.pam_auth_service) > 0: 393 return 1 394 return -1 395 396 if check_password(password, data['password']) > 0: 397 return 1 398 return -1 399 400 # user doesn't exist. now we fail, instead of reserving user. 401 if CFG.disallow_user_creation: 402 raise rhnFault(2001) 403 user, password = check_user_password(user, password) 404 405 # now check the reserved table 406 h = rhnSQL.prepare(""" 407 select r.login, r.password from rhnUserReserved r 408 where r.login_uc = upper(:p1) 409 """) 410 h.execute(p1=user) 411 data = h.fetchone_dict() 412 if data and data["login"]: 413 # found already reserved 414 if check_password(password, data["password"]) > 0: 415 return 1 416 return -2 417 418 validate_new_username(user) 419 log_debug(3, "calling validate_new_password") 420 validate_new_password(password) 421 422 # this is not reserved either, register it 423 if encrypted_password: 424 # Encrypt the password, let the function pick the salt 425 password = encrypt_password(password) 426 427 h = rhnSQL.prepare(""" 428 insert into rhnUserReserved (login, password) 429 values (:username, :password) 430 """) 431 h.execute(username=user, password=password) 432 rhnSQL.commit() 433 434 # all should be dandy 435 return 0
436 437
438 -def new_user(username, password, email, org_id, org_password):
439 """ create a new user account """ 440 return __new_user_db(username, password, email, org_id, org_password)
441 442
443 -def __new_user_db(username, password, email, org_id, org_password):
444 encrypted_password = CFG.encrypted_passwords 445 log_debug(3, username, email, encrypted_password) 446 447 # now search it in the database 448 h = rhnSQL.prepare(""" 449 select w.id, w.password, ui.use_pam_authentication 450 from web_contact w, rhnUserInfo ui 451 where w.login_uc = upper(:username) 452 and w.id = ui.user_id 453 """) 454 h.execute(username=username) 455 data = h.fetchone_dict() 456 457 pre_existing_user = 0 458 459 if not data: 460 # the username is not there, check the reserved user table 461 h = rhnSQL.prepare(""" 462 select login, password from rhnUserReserved 463 where login_uc = upper(:username) 464 """) 465 h.execute(username=username) 466 data = h.fetchone_dict() 467 if not data: # nope, not reserved either 468 raise rhnFault(1, _("Username `%s' has not been reserved") % username) 469 else: 470 pre_existing_user = 1 471 472 if not pre_existing_user and not email: 473 # New accounts have to specify an e-mail address 474 raise rhnFault(30, _("E-mail address not specified")) 475 476 # we have to perform PAM authentication if data has a field called 477 # 'use_pam_authentication' and its value is 'Y', and we do have a PAM 478 # service set in the config file. 479 # Note that if the user is only reserved we don't do PAM authentication 480 if data.get('use_pam_authentication') == 'Y' and CFG.pam_auth_service: 481 # Check the password with PAM 482 import rhnAuthPAM 483 if rhnAuthPAM.check_password(username, password, CFG.pam_auth_service) <= 0: 484 # Bad password 485 raise rhnFault(2) 486 # We don't care about the password anymore, replace it with something 487 import time 488 password = 'pam:%.8f' % time.time() 489 else: 490 # Regular authentication 491 if check_password(password, data["password"]) == 0: 492 # Bad password 493 raise rhnFault(2) 494 495 # creation of user was never supported in spacewalk but this call was mis-used 496 # to check username/password in the past 497 # so let's skip other checks and return now 498 return 0
499 500
501 -def check_user_password(username, password):
502 """ Do some minimal checks on the data thrown our way. """ 503 # username is required 504 if not username: 505 raise rhnFault(11) 506 # password is required 507 if not password: 508 raise rhnFault(12) 509 if len(username) < CFG.MIN_USER_LEN: 510 raise rhnFault(13, _("username should be at least %d characters") 511 % CFG.MIN_USER_LEN) 512 if len(username) > CFG.MAX_USER_LEN: 513 raise rhnFault(700, _("username should be less than %d characters") 514 % CFG.MAX_USER_LEN) 515 username = username[:CFG.MAX_USER_LEN] 516 517 # Invalid characters 518 # ***NOTE*** Must coordinate with web and installer folks about any 519 # changes to this set of characters!!!! 520 invalid_re = re.compile(".*[\s&+%'`\"=#]", re.I) 521 tmp = invalid_re.match(username) 522 if tmp is not None: 523 pos = tmp.regs[0] 524 raise rhnFault(15, _("username = `%s', invalid character `%s'") % ( 525 username, username[pos[1] - 1])) 526 527 # use new password validation method 528 validate_new_password(password) 529 530 return username, password
531 532
533 -def check_email(email):
534 """ Do some minimal checks on the e-mail address """ 535 if email is not None: 536 email = email.strip() 537 538 if not email: 539 # Still supported 540 return None 541 542 if len(email) > CFG.MAX_EMAIL_LEN: 543 raise rhnFault(100, _("Please limit your e-mail address to %s chars") % 544 CFG.MAX_EMAIL_LEN) 545 # XXX More to come (check the format is indeed foo@bar.baz 546 return email
547 548
549 -def check_password(key, pwd1):
550 """ Validates the given key against the current or old password 551 If encrypted_password is false, it compares key with pwd1. 552 If encrypted_password is true, it compares the encrypted key 553 with pwd1. 554 555 Historical note: we used to compare the passwords case-insensitive, and that 556 was working fine until we started to encrypt passwords. -- misa 20030530 557 """ 558 encrypted_password = CFG.encrypted_passwords 559 log_debug(4, "Encrypted password:", encrypted_password) 560 # We don't trust the origin for key, so stringify it 561 key = str(key) 562 if len(key) == 0: 563 # No zero-length passwords accepted 564 return 0 565 566 if not encrypted_password: 567 # Unencrypted passwords 568 if key == pwd1: # good password 569 return 1 570 log_debug(4, "Unencrypted password doesn't match") 571 return 0 # Invalid 572 573 # Crypted passwords in the database 574 if pwd1.find("$5") == 0: # SHA-256 encrypted password 575 if pwd1 == encrypt_password(key, pwd1, 'SHA-256'): 576 return 1 577 elif pwd1.find("$1$") == 0: # MD5 encrypted password 578 if pwd1 == encrypt_password(key, pwd1, 'MD5'): 579 return 1 580 581 log_debug(4, "Encrypted password doesn't match") 582 return 0 # invalid
583 584
585 -def encrypt_password(key, salt=None, method='SHA-256'):
586 """ Encrypt the key 587 If no salt is supplied, generates one (md5-crypt salt) 588 """ 589 590 pw_params = { 591 'MD5': [8, "$1$"], # method: [salt length, prefix] 592 'SHA-256': [16, "$5$"], 593 } 594 595 if not salt: 596 # No salt supplied, generate it ourselves 597 import base64 598 import time 599 import os 600 # Get the first 15 digits after the decimal point from time.time(), and 601 # add the pid too 602 salt = (time.time() % 1) * 1e15 + os.getpid() 603 # base64 it and keep only the first n chars 604 salt = base64.encodestring(str(salt))[:pw_params[method][0]] 605 # slap the magic in front of the salt 606 salt = pw_params[method][1] + salt + '$' 607 salt = str(salt) 608 return crypt.crypt(key, salt)
609 610
611 -def validate_new_password(password):
612 """ Perform all the checks required for new passwords """ 613 log_debug(3, "Entered validate_new_password") 614 # 615 # We're copying the code because we don't want to 616 # invalidate any of the existing passwords. 617 # 618 619 # Validate password based on configurable length 620 # regular expression 621 if not password: 622 raise rhnFault(12) 623 if len(password) < CFG.MIN_PASSWD_LEN: 624 raise rhnFault(14, _("password must be at least %d characters") 625 % CFG.MIN_PASSWD_LEN) 626 if len(password) > CFG.MAX_PASSWD_LEN: 627 raise rhnFault(701, _("Password must be shorter than %d characters") 628 % CFG.MAX_PASSWD_LEN) 629 630 password = password[:CFG.MAX_PASSWD_LEN] 631 invalid_re = re.compile( 632 r"[^ A-Za-z0-9`!@#$%^&*()-_=+[{\]}\\|;:'\",<.>/?~]") 633 asterisks_re = re.compile(r"^\**$") 634 635 # make sure the password isn't all *'s 636 tmp = asterisks_re.match(password) 637 if tmp is not None: 638 raise rhnFault(15, "password cannot be all asterisks '*'") 639 640 # make sure we have only printable characters 641 tmp = invalid_re.search(password) 642 if tmp is not None: 643 pos = tmp.regs[0] 644 raise rhnFault(15, 645 _("password contains character `%s'") % password[pos[1] - 1])
646 647
648 -def validate_new_username(username):
649 """ Perform all the checks required for new usernames. """ 650 log_debug(3) 651 if len(username) < CFG.MIN_NEW_USER_LEN: 652 raise rhnFault(13, _("username should be at least %d characters long") 653 % CFG.MIN_NEW_USER_LEN) 654 655 disallowed_suffixes = CFG.DISALLOWED_SUFFIXES or [] 656 if not isinstance(disallowed_suffixes, type([])): 657 disallowed_suffixes = [disallowed_suffixes] 658 659 log_debug(4, "Disallowed suffixes", disallowed_suffixes) 660 661 for suffix in disallowed_suffixes: 662 if username[-len(suffix):].upper() == suffix.upper(): 663 raise rhnFault(106, _("Cannot register usernames ending with %s") % 664 suffix)
665