""" Classes that represent interface to data in MySQL tables. It is supposed for all this classes to be referenced as _v_ atributes of mysql user folder, so each thread has it's own copy (so there is no need for locks). """ __version__ = "$Rev: 73 $" # $URL: svn://localhost/mysqlUserFolder/trunk/vdb.py $ # # $LastChangedBy: vladap $ # $LastChangedDate: 2005-09-26 00:29:12 +0300 (Mon, 26 Sep 2005) $ import time, random, array, string from db import dbConnection import c_classes, cfg, cache, util, passwords # --------------------------------------------------------------------- */ # UserDb class class UserDb: """ Class that represents (local) interface to User database. """ def __init__ (Self, db, realm): Self.db = db Self.realm = realm if cfg.CACHE_USERS: Self.user_cache = cache.v_cache (cfg.CACHE_USERS_LIFE) Self.username_cache = cache.v_cache (cfg.CACHE_USERS_LIFE) def list_usernames (Self, role = None): if role is None: return Self.db.list_usernames (Self.realm) else: return Self.db.list_usernames_with_role (Self.realm, role) def list_rolenames (Self): return Self.db.list_rolenames (Self.realm) def auto_create_roles (Self, roles): db_roles = Self.list_rolenames () for r in roles: if not r in db_roles: Self.create_role (r) db_roles.append (r) def refresh_user (Self, user): if cfg.DEBUG: util.log_debug ("refresh_user (): '%s'" % user.getUserName ()) ret = Self.db.get_user_data (user) if (not ret): return ret if cfg.CACHE_USERS: # This will clean the local thread cache # It will not clean caches of other threads uid = user.getDBId () username = user.getUserName () if Self.user_cache.get (uid): Self.user_cache.delete (uid) if Self.username_cache.get (username): Self.username_cache.delete (username) return ret def get_user_count (Self): return Self.db.count_users (Self.realm) def get_role_count (Self): return Self.db.count_roles (Self.realm) def get_user (Self, username): if cfg.DEBUG: util.log_debug ("get_user (): Begin (%s)" % username) if cfg.CACHE_USERS: uid = Self.username_cache.get (username) if not (uid is None): user = Self.user_cache.get (uid) else: user = None if not (user is None): if cfg.DEBUG: util.log_debug ("get_user (): getting user from a cache.") return user._copy () user = c_classes.mysqlUser (username, Self.realm) if not Self.db.get_user_data (user): user = None else: if cfg.CACHE_USERS: uid = user.getDBId () Self.username_cache.add (username, uid) Self.user_cache.add (uid, user._copy ()) if cfg.DEBUG: util.log_debug ("get_user (): End") return user def get_user_by_id (Self, user_id): if cfg.DEBUG: util.log_debug ("get_user_by_id (): Begin (%s)" % user_id) if cfg.CACHE_USERS: user = Self.user_cache.get (user_id) if not (user is None): if cfg.DEBUG: util.log_debug ("get_user_by_id (): " "getting user from a cache.") return user._copy () user = c_classes.mysqlUser ("", Self.realm) user._setDBId (user_id) if not Self.db.get_user_data (user, 1): user = None else: if cfg.CACHE_USERS: Self.user_cache.add (user_id, user._copy ()) if cfg.DEBUG: util.log_debug ("get_user_by_id (): End") return user def change_user_password (Self, user, new_pass, ptype = None): """ Changes user password. If password type is not specified, current type will be used. """ if ptype is None: ptype = user.getPasswordType () if ptype == cfg.VDB_INVALID_PASSWORD_TYPE: return 0 if ptype != 0: new_pass = passwords.encode (new_pass, ptype) ret = Self.db.change_user_password (user, new_pass, ptype) return ret def change_user_roles (Self, user, roles): roles = filter (lambda x: x not in cfg.ZOPE_SYSTEM_ROLES, roles) if cfg.AUTO_CREATE_ROLES: Self.auto_create_roles (roles) ret = Self.db.change_user_roles (user, roles) Self.refresh_user (user) return ret def change_user_other (Self, user, realname, email): ret = Self.db.change_user_other (user, realname, email) Self.refresh_user (user) return ret def change_user_domains (Self, user, domains): ret = Self.db.change_user_domains (user, domains) Self.refresh_user (user) return ret def create_user (Self, username, roles): if cfg.AUTO_CREATE_ROLES: Self.auto_create_roles (roles) ok = Self.db.create_user (username, Self.realm, roles) if ok: user = Self.get_user (username) else: user = None return user def del_user (Self, username): ok = Self.db.del_user (username, Self.realm, 'user') return ok def create_role (Self, role): ok = Self.db.create_role (role, Self.realm) return ok def del_role (Self, role): ok = Self.db.del_role (role, Self.realm) return ok # --------------------------------------------------------------------- */ # SessionDb class. class SessionDb: """ Class that represents (local) interface to the session db. This class will cache sessions only if they belog to a valid user. Otherwise, some threads might have outdated session's user id in a cache. """ def __init__ (Self, db): Self.db = db if cfg.CACHE_SESSIONS: Self.session_cache = cache.v_cache (cfg.CACHE_SESSIONS_LIFE) def create_session (Self, user_id): s = c_classes.mysqlSession () s._setUserId (user_id) Self.db.create_session (s) if user_id != cfg.VDB_INVALID_ID: if cfg.CACHE_SESSIONS: Self.session_cache.add (s.id, s._copy ()) return s def get_session_by_id (Self, id): if cfg.CACHE_SESSIONS: o = Self.session_cache.get (id) if not (o is None): return o._copy () s = c_classes.mysqlSession () s._setId (id) Self.db.get_session (s) if s.getId () == cfg.VDB_INVALID_ID: return None if (cfg.CACHE_SESSIONS) and (s.getUserId () != cfg.VDB_INVALID_ID): Self.session_cache.add (id, s._copy ()) return s def assign_user_id (Self, session, user_id): assert (session.user_id == cfg.VDB_INVALID_ID) Self.db.session_set_user_id (session, user_id) session.user_id = user_id # --------------------------------------------------------------------- */ # TokenDb class class HTTPCookieInfo: pass class TokenDb: """ This class defines API for token validation. Tokens are identified by (class_id, token_id). Random value is assigned to a token when it is created. Life and timeout values are also recorded for tokens. There are three general functions for tokens (create, delete, check) and three functions that save tokens as cookies. """ def __init__ (Self, db, realm, http_cookie_info): Self.db = db Self.realm = realm Self.http_cookie_info = http_cookie_info if cfg.CACHE_TOKENS: Self.token_caches = {} def __get_cache_db (Self, class_id): d = Self.token_caches.get (class_id, None) if d is None: d = cache.v_cache (cfg.CACHE_TOKENS_LIFE) Self.token_caches [class_id] = d return d def __mk_cookie_names (Self, class_id): if Self.http_cookie_info.use_realm: bname = '__ac_%s_%s' % (Self.realm, class_id) else: bname = '__ac_%s' % (class_id) n1 = bname n2 = bname + '_value' return (n1, n2) def __mk_random_value (Self): a = array.array ('b') for i in range (cfg.TOKEN_RNDSTR_LEN): c = random.randrange (ord ('a'), ord ('z') + 1) a.append (c) return a.tostring () def __mk_cookie_time_str (Self, ttuple): fmt = "%s, %s-%s-%s %s:%s:%s GMT" dn = cfg.COOKIE_DAY_NAMES [ttuple [6] - 1] mn = cfg.COOKIE_MONTH_NAMES [ttuple [1] - 1] sec = ttuple [5] if sec > 59: sec = 59 s = fmt % (dn, ttuple [2], mn, ttuple [0], ttuple [3], ttuple [4], sec) return s def __check_token_expired (Self, token_tuple, ctime): (db_token_value, db_token_created, db_token_accessed, db_token_timeout, db_token_life) = token_tuple if db_token_life != -1: if ctime > (db_token_created + db_token_life): return 1 if db_token_timeout != -1: if ctime > (db_token_accessed + db_token_timeout): return 1 return 0 def __check_token_value (Self, token_tuple, value): (db_token_value, db_token_created, db_token_accessed, db_token_timeout, db_token_life) = token_tuple if db_token_value != value: return 0 return 1 def delete_token (Self, class_id, token_id): if cfg.CACHE_TOKENS: cache_db = Self.__get_cache_db (class_id) cache_db.delete (token_id) Self.db.del_token (class_id, token_id, Self.realm) def assign_new_token (Self, class_id, token_id, token_timeout, token_life): token_value = Self.__mk_random_value () created_time = time.time () accessed_time = created_time Self.db.create_token (class_id, token_id, token_value, token_timeout, token_life, created_time, accessed_time, Self.realm) return token_value def check_token (Self, class_id, token_id, token_value): ctime = time.time () r = None found_in_cache = 0 if cfg.CACHE_TOKENS: cache_db = Self.__get_cache_db (class_id) r = cache_db.get (token_id) if r: token_ok = ((not Self.__check_token_expired (r, ctime)) and Self.__check_token_value (r, token_value)) if not token_ok: cache_db.delete (token_id) r = None else: found_in_cache = 1 if not found_in_cache: r = Self.db.get_token (class_id, token_id, Self.realm) if r is None: return cfg.VDB_INVALID_ID (db_token_value, db_token_created, db_token_accessed, db_token_timeout, db_token_life) = r if not Self.__check_token_value (r, token_value): util.log_debug ("check_token (%s, %s): Invalid token: " "value: '%s' != '%s'" \ % (class_id, token_id, db_token_value, token_value)) return cfg.VDB_INVALID_ID if not found_in_cache: if Self.__check_token_expired (r, ctime): return cfg.VDB_INVALID_ID Self.db.update_token_accessed (class_id, token_id, ctime, Self.realm) # Access time is not updated for tokens in cache if cfg.CACHE_TOKENS and (not found_in_cache): cache_db.add (token_id, r) return token_id def request_delete_token (Self, RESPONSE, class_id, token_id): (n1, n2) = Self.__mk_cookie_names (class_id) cookie_path = Self.http_cookie_info.path cookie_domain = Self.http_cookie_info.domain if cookie_domain: RESPONSE.expireCookie (n1, path = cookie_path, domain = cookie_domain) RESPONSE.expireCookie (n2, path = cookie_path, domain = cookie_domain) else: RESPONSE.expireCookie (n1, path = cookie_path) RESPONSE.expireCookie (n2, path = cookie_path) Self.delete_token (class_id, token_id) def request_assign_new_token (Self, RESPONSE, class_id, token_id, token_timeout, token_life, request_persistent): """ Creates new request (HTTP) token. This function will create a new token in the MySQL database and it will assign it to current request using setCookie () method. It request_persistent is false, expire parameter will not be passed to setCookie () maing cookies dissapear when browser ic closed. """ (n1, n2) = Self.__mk_cookie_names (class_id) token_value = Self.assign_new_token (class_id, token_id, token_timeout, token_life) token_value_str = token_value token_id_str = "%i" % token_id if request_persistent: if token_life < cfg.COOKIE_MIN_EXPIRE: token_life = cfg.COOKIE_MIN_EXPIRE else: token_life = -1 if token_life != -1: created_time = time.time () exp_time = created_time + token_life exp_time_str = Self.__mk_cookie_time_str (time.gmtime (exp_time)) cookie_path = Self.http_cookie_info.path cookie_domain = Self.http_cookie_info.domain arg_list_1 = [n1, token_id_str] arg_list_2 = [n2, token_value_str] arg_dict = {} arg_dict ['path'] = cookie_path if cookie_domain: arg_dict ['domain'] = cookie_domain if request_persistent: arg_dict ['expires'] = exp_time_str apply (RESPONSE.setCookie, arg_list_1, arg_dict) apply (RESPONSE.setCookie, arg_list_2, arg_dict) def request_check_token (Self, REQUEST, class_id): (n1, n2) = Self.__mk_cookie_names (class_id) found = 0 if REQUEST.cookies.has_key (n1) and REQUEST.cookies.has_key (n2): token_id = REQUEST.cookies [n1] token_value = REQUEST.cookies [n2] found = 1 try: token_id = string.atol (token_id, 10) except: found = 0 if not found: util.log_debug ("request_check_token (%s): no token in " "REQUEST." % class_id) return cfg.VDB_INVALID_ID else: util.log_debug ("request_check_token (%s): " "Found token: '%s' = '%s'" % (class_id, token_id, token_value)) token_ok = Self.check_token (class_id, token_id, token_value) return token_ok # --------------------------------------------------------------------- */ # MiscData database class MiscDataDB: """ API for accessing misc data. Each object (identified by class and id) can have arbitrary number of miscdata tuples identified by id. """ def __init__ (Self, db): Self.db = db def save_data (Self, obj_class, obj_id, misc_id, misc_tuple): Self.db.save_misc_data (obj_class, obj_id, misc_id, misc_tuple) def get_data (Self, obj_class, obj_id, misc_id): return Self.db.get_misc_data (obj_class, obj_id, misc_id) def del_data (Self, obj_class, obj_id, misc_id): Self.db.del_misc_data (obj_class, obj_id, misc_id)