# USA.
from random import seed, sample
-from string import letters, digits
+from string import ascii_letters, digits
from osv import fields,osv
import pooler
-import tools
+from tools.translate import _
from service import security
magic_md5 = '$1$'
-def gen_salt( length=8, symbols=letters + digits ):
+def gen_salt( length=8, symbols=ascii_letters + digits ):
seed()
return ''.join( sample( symbols, length ) )
# *
# * Poul-Henning Kamp
-import md5
+
+#TODO: py>=2.6: from hashlib import md5
+import hashlib
def encrypt_md5( raw_pw, salt, magic=magic_md5 ):
- hash = md5.new( raw_pw + magic + salt )
- stretch = md5.new( raw_pw + salt + raw_pw).digest()
+ hash = hashlib.md5()
+ hash.update( raw_pw + magic + salt )
+ st = hashlib.md5()
+ st.update( raw_pw + salt + raw_pw)
+ stretch = st.digest()
for i in range( 0, len( raw_pw ) ):
hash.update( stretch[i % 16] )
saltedmd5 = hash.digest()
for i in range( 1000 ):
- hash = md5.new()
+ hash = hashlib.md5()
if i & 1:
hash.update( raw_pw )
return magic + salt + '$' + rearranged
-_salt_cache = {}
-
-def login(db, login, password):
- cr = pooler.get_db(db).cursor()
- cr.execute( 'select password from res_users where login=%s', (login.encode( 'utf-8' ),) )
- stored_pw = cr.fetchone()
-
- if stored_pw:
- stored_pw = stored_pw[0]
- else:
- # Return early if no one has a login name like that.
- return False
-
- # Calculate a new password ('updated_pw') from 'stored_pw' if the
- # latter isn't encrypted yet. Use that to update the database entry.
- # Also update the 'stored_pw' to reflect the change.
-
- if stored_pw[0:3] != magic_md5:
- updated_pw = encrypt_md5( stored_pw, gen_salt() )
- cr.execute( 'update res_users set password=%s where login=%s', (updated_pw.encode( 'utf-8' ), login.encode( 'utf-8' ),) )
- cr.commit()
-
- cr.execute( 'select password from res_users where login=%s', (login.encode( 'utf-8' ),) )
- stored_pw = cr.fetchone()[0]
-
- # Calculate an encrypted password from the user-provided
- # password ('encrypted_pw').
-
- salt = _salt_cache[password] = stored_pw[3:11]
- encrypted_pw = encrypt_md5( password, salt )
-
- # Retrieve a user id from the database, factoring in an encrypted
- # password.
-
- cr.execute('select id from res_users where login=%s and password=%s and active', (login.encode('utf-8'), encrypted_pw.encode('utf-8')))
- res = cr.fetchone()
- cr.close()
-
- if res:
- return res[0]
- else:
- return False
-
-#def check_super(passwd):
-# salt = _salt_cache[passwd]
-# if encrypt_md5( passwd, salt ) == tools.config['admin_passwd']:
-# return True
-# else:
-# raise Exception('AccessDenied')
-
-def check(db, uid, passwd):
- if security._uid_cache.has_key( uid ) and (security._uid_cache[uid]==passwd):
- return True
- cr = pooler.get_db(db).cursor()
- salt = _salt_cache[passwd]
- cr.execute(' select count(*) from res_users where id=%d and password=%s', (int(uid), encrypt_md5( passwd, salt )) )
- res = cr.fetchone()[0]
- cr.close()
- if not bool(res):
- raise Exception('AccessDenied')
- if res:
- security._uid_cache[uid] = passwd
- return bool(res)
-
-
-def access(db, uid, passwd, sec_level, ids):
- cr = pooler.get_db(db).cursor()
- salt = _salt_cache[passwd]
- cr.execute('select id from res_users where id=%s and password=%s', (uid, encrypt_md5( passwd, salt )) )
- res = cr.fetchone()
- cr.close()
- if not res:
- raise Exception('Bad username or password')
- return res[0]
-
-# check if module is installed or not
-security.login=login
-#security.check_super=check_super
-security.access=access
-security.check=check
-
class users(osv.osv):
_name="res.users"
_inherit="res.users"
# agi - 022108
# Add handlers for 'input_pw' field.
- def set_pw( self, cr, uid, id, name, value, args, context ):
- self.write( cr, uid, id, { 'password' : encrypt_md5( value, gen_salt() ) } )
+ def set_pw(self, cr, uid, id, name, value, args, context):
+ if not value:
+ raise osv.except_osv(_('Error'), _("Please specify the password !"))
+
+ obj = pooler.get_pool(cr.dbname).get('res.users')
+ if not hasattr(obj, "_salt_cache"):
+ obj._salt_cache = {}
+
+ salt = obj._salt_cache[id] = gen_salt()
+ encrypted = encrypt_md5(value, salt)
+ cr.execute('update res_users set password=%s where id=%s',
+ (encrypted.encode('utf-8'), int(id)))
+ cr.commit()
del value
def get_pw( self, cr, uid, ids, name, args, context ):
+ cr.execute('select id, password from res_users where id in %s', (tuple(map(int, ids)),))
+ stored_pws = cr.fetchall()
res = {}
- for id in ids:
- res[id] = ''
- return res
- # Continuing to original code.
+ for id, stored_pw in stored_pws:
+ res[id] = stored_pw
+
+ return res
_columns = {
- 'input_pw': fields.function( get_pw, fnct_inv=set_pw, type='char', method=True, size=20, string='Password', invisible=True),
- }
-users()
\ No newline at end of file
+ # The column size could be smaller as it is meant to store a hash, but
+ # an existing column cannot be downsized; thus we use the original
+ # column size.
+ 'password': fields.function(get_pw, fnct_inv=set_pw, type='char',
+ method=True, size=64, string='Password', invisible=True,
+ store=True),
+ }
+
+ def login(self, db, login, password):
+ if not password:
+ return False
+ if db is False:
+ raise RuntimeError("Cannot authenticate to False db!")
+ cr = None
+ try:
+ cr = pooler.get_db(db).cursor()
+ return self._login(cr, db, login, password)
+ except Exception:
+ import logging
+ logging.getLogger('netsvc').exception('Could not authenticate')
+ return Exception('Access Denied')
+ finally:
+ if cr is not None:
+ cr.close()
+
+ def _login(self, cr, db, login, password):
+ cr.execute( 'SELECT password, id FROM res_users WHERE login=%s AND active',
+ (login.encode('utf-8'),))
+
+ if cr.rowcount:
+ stored_pw, id = cr.fetchone()
+ else:
+ # Return early if no one has a login name like that.
+ return False
+
+ stored_pw = self.maybe_encrypt(cr, stored_pw, id)
+
+ if not stored_pw:
+ # means couldn't encrypt or user is not active!
+ return False
+
+ # Calculate an encrypted password from the user-provided
+ # password.
+ obj = pooler.get_pool(db).get('res.users')
+ if not hasattr(obj, "_salt_cache"):
+ obj._salt_cache = {}
+ salt = obj._salt_cache[id] = stored_pw[len(magic_md5):11]
+ encrypted_pw = encrypt_md5(password, salt)
+
+ # Check if the encrypted password matches against the one in the db.
+ cr.execute('UPDATE res_users SET date=now() ' \
+ 'WHERE id=%s AND password=%s AND active RETURNING id',
+ (int(id), encrypted_pw.encode('utf-8')))
+ res = cr.fetchone()
+ cr.commit()
+
+ if res:
+ return res[0]
+ else:
+ return False
+
+ def check(self, db, uid, passwd):
+ if not passwd:
+ # empty passwords disallowed for obvious security reasons
+ raise security.ExceptionNoTb('AccessDenied')
+
+ # Get a chance to hash all passwords in db before using the uid_cache.
+ obj = pooler.get_pool(db).get('res.users')
+ if not hasattr(obj, "_salt_cache"):
+ obj._salt_cache = {}
+ self._uid_cache.get(db, {}).clear()
+
+ cached_pass = self._uid_cache.get(db, {}).get(uid)
+ if (cached_pass is not None) and cached_pass == passwd:
+ return True
+
+ cr = pooler.get_db(db).cursor()
+ try:
+ if uid not in self._salt_cache.get(db, {}):
+ # If we don't have cache, we have to repeat the procedure
+ # through the login function.
+ cr.execute( 'SELECT login FROM res_users WHERE id=%s', (uid,) )
+ stored_login = cr.fetchone()
+ if stored_login:
+ stored_login = stored_login[0]
+
+ res = self._login(cr, db, stored_login, passwd)
+ if not res:
+ raise security.ExceptionNoTb('AccessDenied')
+ else:
+ salt = self._salt_cache[db][uid]
+ cr.execute('SELECT COUNT(*) FROM res_users WHERE id=%s AND password=%s AND active',
+ (int(uid), encrypt_md5(passwd, salt)))
+ res = cr.fetchone()[0]
+ finally:
+ cr.close()
+
+ if not bool(res):
+ raise security.ExceptionNoTb('AccessDenied')
+
+ if res:
+ if self._uid_cache.has_key(db):
+ ulist = self._uid_cache[db]
+ ulist[uid] = passwd
+ else:
+ self._uid_cache[db] = {uid: passwd}
+ return bool(res)
+
+ def maybe_encrypt(self, cr, pw, id):
+ """ Return the password 'pw', making sure it is encrypted.
+
+ If the password 'pw' is not encrypted, then encrypt all active passwords
+ in the db. Returns the (possibly newly) encrypted password for 'id'.
+ """
+
+ if not pw.startswith(magic_md5):
+ cr.execute("SELECT id, password FROM res_users " \
+ "WHERE active=true AND password NOT LIKE '$%'")
+ # Note that we skip all passwords like $.., in anticipation for
+ # more than md5 magic prefixes.
+ res = cr.fetchall()
+ for i, p in res:
+ encrypted = encrypt_md5(p, gen_salt())
+ cr.execute('UPDATE res_users SET password=%s where id=%s',
+ (encrypted, i))
+ if i == id:
+ encrypted_res = encrypted
+ cr.commit()
+ return encrypted_res
+ return pw
+
+users()
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: