Auth(...,salt=True), thanks Dave Stoll
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.00.0 (2012-07-04 17:45:52) dev
|
||||
Version 2.00.0 (2012-07-04 19:22:37) dev
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</li><li>Corne Dickens (import mechanism)
|
||||
</li><li>Craig Younkins (Security)
|
||||
</li><li>Daniel Lin (Taiwanese internationalization)
|
||||
</li><li>Dave Stoll (DowCommerce payment API)
|
||||
</li><li>Dave Stoll (DowCommerce payment API, security)
|
||||
</li><li>David Wagner (security and cryptography expert)
|
||||
</li><li>Denes Lengyel (validators, DB2 support, DAL, custom forms, legacy table support)
|
||||
</li><li>Douglas Soares de Andrade (2.4 and 2.6 compliance, docstrings)
|
||||
|
||||
@@ -40,7 +40,7 @@ response.generic_patterns = ['*'] if request.is_local else []
|
||||
#########################################################################
|
||||
|
||||
from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
|
||||
auth = Auth(db, hmac_key=Auth.get_or_create_key())
|
||||
auth = Auth(db, hmac_key=Auth.get_or_create_key(), salt=True)
|
||||
crud, service, plugins = Crud(db), Service(), PluginManager()
|
||||
|
||||
## create all tables needed by auth if not custom tables
|
||||
|
||||
@@ -1 +1 @@
|
||||
d879ea69-ddb6-4c82-8eab-af68c53de7c4
|
||||
sha512:9d649235-cf34-4b0c-b103-28e985bc3d40
|
||||
+1
-1
@@ -8152,7 +8152,7 @@ class Field(Expression):
|
||||
def __str__(self):
|
||||
quote = self.db._adapter.varquote
|
||||
try:
|
||||
return '%s.%s' % (varquote(self.tablename), quote(self.name))
|
||||
return '%s.%s' % (quote(self.tablename), quote(self.name))
|
||||
except:
|
||||
return '<no table>.%s' % quote(self.name)
|
||||
|
||||
|
||||
+27
-25
@@ -880,7 +880,7 @@ class Auth(object):
|
||||
def here(self):
|
||||
return URL(args=current.request.args,vars=current.request.vars)
|
||||
|
||||
def __init__(self, environment=None, db=None, mailer=True,
|
||||
def __init__(self, environment=None, db=None, mailer=True, salt = False,
|
||||
hmac_key=None, controller='default', function='user', cas_provider=None):
|
||||
"""
|
||||
auth=Auth(db)
|
||||
@@ -922,6 +922,7 @@ class Auth(object):
|
||||
|
||||
settings.hideerror = False
|
||||
settings.password_min_length = 4
|
||||
settings.salt = salt
|
||||
settings.cas_domains = [request.env.http_host]
|
||||
settings.cas_provider = cas_provider
|
||||
settings.cas_actions = {'login':'login',
|
||||
@@ -1367,7 +1368,7 @@ class Auth(object):
|
||||
table.username.requires = \
|
||||
[IS_MATCH('[\w\.\-]+'),
|
||||
IS_NOT_IN_DB(db, table.username)]
|
||||
if not self.settings.username_case_sensitive:
|
||||
if not settings.username_case_sensitive:
|
||||
table.username.requires.insert(1,IS_LOWER())
|
||||
else:
|
||||
table = db.define_table(
|
||||
@@ -1400,12 +1401,12 @@ class Auth(object):
|
||||
table.last_name.requires = \
|
||||
IS_NOT_EMPTY(error_message=self.messages.is_empty)
|
||||
table[passfield].requires = [
|
||||
CRYPT(key=settings.hmac_key,
|
||||
min_length=self.settings.password_min_length)]
|
||||
CRYPT(key=settings.hmac_key,salt=settings.salt,
|
||||
min_length=settings.password_min_length)]
|
||||
table.email.requires = \
|
||||
[IS_EMAIL(error_message=self.messages.invalid_email),
|
||||
IS_NOT_IN_DB(db, table.email)]
|
||||
if not self.settings.email_case_sensitive:
|
||||
if not settings.email_case_sensitive:
|
||||
table.email.requires.insert(1,IS_LOWER())
|
||||
table.registration_key.default = ''
|
||||
settings.table_user = db[settings.table_user_name]
|
||||
@@ -1523,16 +1524,16 @@ class Auth(object):
|
||||
settings.actions_disabled = \
|
||||
['profile','register','change_password','request_reset_password']
|
||||
from gluon.contrib.login_methods.cas_auth import CasAuth
|
||||
maps = self.settings.cas_maps
|
||||
maps = settings.cas_maps
|
||||
if not maps:
|
||||
maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
|
||||
settings.table_user.fields if name!='id' \
|
||||
and settings.table_user[name].readable)
|
||||
maps['registration_id'] = \
|
||||
lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user'])
|
||||
actions = [self.settings.cas_actions['login'],
|
||||
self.settings.cas_actions['servicevalidate'],
|
||||
self.settings.cas_actions['logout']]
|
||||
actions = [settings.cas_actions['login'],
|
||||
settings.cas_actions['servicevalidate'],
|
||||
settings.cas_actions['logout']]
|
||||
settings.login_form = CasAuth(
|
||||
casversion = 2,
|
||||
urlbase = settings.cas_provider,
|
||||
@@ -1615,7 +1616,7 @@ class Auth(object):
|
||||
user = self.db(table_user[userfield] == username).select().first()
|
||||
if user:
|
||||
password = table_user[passfield].validate(password)[0]
|
||||
if not user.registration_key and user[passfield] == password:
|
||||
if not user.registration_key and password == user[passfield]:
|
||||
user = Storage(table_user._filter_fields(user, id=True))
|
||||
session.auth = Storage(user=user, last_visit=request.now,
|
||||
expiration=self.settings.expiration,
|
||||
@@ -1855,7 +1856,7 @@ class Auth(object):
|
||||
# alternates have failed, maybe because service inaccessible
|
||||
if self.settings.login_methods[0] == self:
|
||||
# try logging in locally using cached credentials
|
||||
if temp_user[passfield] == form.vars.get(passfield, ''):
|
||||
if form.vars.get(passfield, '') == temp_user[passfield]:
|
||||
# success
|
||||
user = temp_user
|
||||
else:
|
||||
@@ -2262,7 +2263,7 @@ class Auth(object):
|
||||
redirect(self.url(args=request.args))
|
||||
password = self.random_password()
|
||||
passfield = self.settings.password_field
|
||||
d = {passfield: table_user[passfield].validate(password)[0],
|
||||
d = {passfield: str(table_user[passfield].validate(password)[0]),
|
||||
'registration_key': ''}
|
||||
user.update_record(**d)
|
||||
if self.settings.mailer and \
|
||||
@@ -2329,7 +2330,7 @@ class Auth(object):
|
||||
separator=self.settings.label_separator
|
||||
)
|
||||
if form.accepts(request,session,hideerror=self.settings.hideerror):
|
||||
user.update_record(**{passfield:form.vars.new_password,
|
||||
user.update_record(**{passfield:str(form.vars.new_password),
|
||||
'registration_key':'',
|
||||
'reset_password_key':''})
|
||||
session.flash = self.messages.password_changed
|
||||
@@ -2466,10 +2467,7 @@ class Auth(object):
|
||||
form = SQLFORM.factory(
|
||||
Field('old_password', 'password',
|
||||
label=self.messages.old_password,
|
||||
requires=validators(
|
||||
table_user[passfield].requires,
|
||||
IS_IN_DB(s, '%s.%s' % (usern, passfield),
|
||||
error_message=self.messages.invalid_password))),
|
||||
requires=table_user[passfield].requires),
|
||||
Field('new_password', 'password',
|
||||
label=self.messages.new_password,
|
||||
requires=table_user[passfield].requires),
|
||||
@@ -2486,15 +2484,19 @@ class Auth(object):
|
||||
formname='change_password',
|
||||
onvalidation=onvalidation,
|
||||
hideerror=self.settings.hideerror):
|
||||
d = {passfield: form.vars.new_password}
|
||||
s.update(**d)
|
||||
session.flash = self.messages.password_changed
|
||||
self.log_event(log, self.user)
|
||||
callback(onaccept,form)
|
||||
if not next:
|
||||
next = self.url(args=request.args)
|
||||
|
||||
if not form.vars['old_password'] == s.select().first()[passfield]:
|
||||
form.errors['old_password'] = self.messages.invalid_password
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
d = {passfield: str(form.vars.new_password)}
|
||||
s.update(**d)
|
||||
session.flash = self.messages.password_changed
|
||||
self.log_event(log, self.user)
|
||||
callback(onaccept,form)
|
||||
if not next:
|
||||
next = self.url(args=request.args)
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
redirect(next)
|
||||
return form
|
||||
|
||||
|
||||
+61
-5
@@ -2519,6 +2519,49 @@ class CLEANUP(Validator):
|
||||
v = self.regex.sub('',str(value).strip())
|
||||
return (v, None)
|
||||
|
||||
class LazyCrypt(object):
|
||||
"""
|
||||
Stores a lazy password hash
|
||||
"""
|
||||
def __init__(self,crypt,password):
|
||||
"""
|
||||
crypt is an instance of the CRYPT validator,
|
||||
password is the password as inserted by the user
|
||||
"""
|
||||
self.crypt = crypt
|
||||
self.password = password
|
||||
self.crypted = None
|
||||
def __str__(self):
|
||||
"""
|
||||
Encrypted self.password and caches it in self.crypted.
|
||||
If self.crypt.salt the output is in the format <algorithm>$<salt>$<hash>
|
||||
"""
|
||||
if self.crypted:
|
||||
return self.crypted
|
||||
if self.crypt.salt:
|
||||
if self.crypt.salt == True:
|
||||
salt = str(web2py_uuid()).replace('-','')[-16:]
|
||||
else:
|
||||
salt = self.crypt.salt
|
||||
self.crypted = '%s$%s$%s' % \
|
||||
(self.crypt.digest_alg, salt,
|
||||
simple_hash(self.password+salt, self.crypt.digest_alg))
|
||||
elif self.crypt.key:
|
||||
self.crypted = hmac_hash(self.password, self.crypt.key, self.crypt.digest_alg)
|
||||
else:
|
||||
self.crypted = simple_hash(self.password, self.crypt.digest_alg)
|
||||
return self.crypted
|
||||
def __eq__(self, stored_password):
|
||||
"""
|
||||
compares the current lazy crypted password with a stored password
|
||||
"""
|
||||
if self.crypt.salt and stored_password.count('$')==2:
|
||||
(algorithm, salt, hash) = stored_password.split('$')
|
||||
temp_pass = '%s$%s$%s' % (algorithm, salt, simple_hash(self.password+salt, algorithm))
|
||||
else:
|
||||
temp_pass = str(self)
|
||||
return temp_pass == stored_password
|
||||
|
||||
|
||||
class CRYPT(object):
|
||||
"""
|
||||
@@ -2539,21 +2582,34 @@ class CRYPT(object):
|
||||
|
||||
Notice that an empty password is accepted but invalid. It will not allow login back.
|
||||
Stores junk as hashed password.
|
||||
|
||||
Specify an algorithm or by default we will use sha512.
|
||||
|
||||
Typical available algorithms:
|
||||
md5, sha1, sha224, sha256, sha384, sha512
|
||||
|
||||
If salt, it hashes a password with a salt.
|
||||
If salt is True, this method will automatically generate one.
|
||||
Either case it returns an encrypted password string in the following format:
|
||||
|
||||
<algorithm>$<salt>$<hash>
|
||||
|
||||
Important: hashed password is returned as a LazyCrypt object and computed only if needed.
|
||||
The LasyCrypt object also knows how to compare itself with an existing salted password
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, key=None, digest_alg='md5', min_length=0, error_message='too short'):
|
||||
def __init__(self, key=None, digest_alg='md5', min_length=0, error_message='too short', salt=None):
|
||||
self.key = key
|
||||
self.digest_alg = digest_alg
|
||||
self.min_length = min_length
|
||||
self.error_message = error_message
|
||||
self.salt = salt
|
||||
|
||||
def __call__(self, value):
|
||||
if len(value)<self.min_length:
|
||||
return ('', translate(self.error_message))
|
||||
if self.key:
|
||||
return (hmac_hash(value, self.key, self.digest_alg), None)
|
||||
else:
|
||||
return (simple_hash(value, self.digest_alg), None)
|
||||
return (LazyCrypt(self,value),None)
|
||||
|
||||
|
||||
class IS_STRONG(object):
|
||||
|
||||
Reference in New Issue
Block a user