summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDonald Stufft <donald@stufft.io>2013-02-12 00:43:17 -0500
committerDonald Stufft <donald@stufft.io>2013-02-12 00:43:17 -0500
commit622d5b0defc2c08e58a5544c0423cc7d98538cf3 (patch)
tree6780721d5cef359ccee4ab53a51bda6834cd3d6b
parent22e57b4405e4a94810d472b89fddad1b3b26f91e (diff)
downloaddecorator-622d5b0defc2c08e58a5544c0423cc7d98538cf3.tar.gz
Rewrite password hashing to utilize passlib + bcrypt
* Upon logging in the existing unsalted sha1 passwords will be upgraded to bcrypt * PyPI will prefer using cookie auth to prevent needing to do bcrypt on every request * Load passlib configuration from the existing config.ini file
-rw-r--r--config.ini.template5
-rw-r--r--config.py12
-rw-r--r--store.py10
-rw-r--r--webui.py78
4 files changed, 69 insertions, 36 deletions
diff --git a/config.ini.template b/config.ini.template
index 6276971..93d42ed 100644
--- a/config.ini.template
+++ b/config.ini.template
@@ -24,6 +24,11 @@ cheesecake_password = secret
key_dir = .
simple_sign_script = /serversig
+[passlib]
+; The first listed schemed will automatically be the default, see passlib
+; documentation for a full list of options.
+schemes = bcrypt, hex_sha1
+
[logging]
file =
mailhost =
diff --git a/config.py b/config.py
index b8922bc..d8489f1 100644
--- a/config.py
+++ b/config.py
@@ -1,6 +1,9 @@
import ConfigParser
from urlparse import urlsplit, urlunsplit
+from passlib.context import CryptContext
+
+
class Config:
''' Read in the config and set up the vars with the correct type.
'''
@@ -61,6 +64,15 @@ class Config:
self.sentry_dsn = c.get('sentry', 'dsn')
+ self.passlib = CryptContext(
+ # Unless we've manually specific a list of deprecated
+ # algorithms assume we will deprecate all but the default.
+ deprecated=["auto"],
+ )
+
+ # Configure a passlib context from the config file
+ self.passlib.load_path(configfile, update=True)
+
def make_https(self):
if self.url.startswith("http:"):
self.url = "https"+self.url[4:]
diff --git a/store.py b/store.py
index 786fc66..0c71df8 100644
--- a/store.py
+++ b/store.py
@@ -1336,7 +1336,7 @@ class Store:
if self.has_user(name):
if password:
# update existing user, including password
- password = hashlib.sha1(password).hexdigest()
+ password = self.config.passlib.encrypt(password)
safe_execute(cursor,
'update users set password=%s, email=%s where name=%s',
(password, email, name))
@@ -1355,7 +1355,7 @@ class Store:
if cursor.fetchone()[0] > 0:
raise ValueError, "Email address already belongs to a different user"
- password = hashlib.sha1(password).hexdigest()
+ password = self.config.passlib.encrypt(password)
# new user
safe_execute(cursor,
@@ -2129,8 +2129,10 @@ class Store:
update users set last_login=current_timestamp where name=%s''', (username,))
self.userip = userip
- def setpasswd(self, username, password):
- password = hashlib.sha1(password).hexdigest()
+ def setpasswd(self, username, password, hashed=False):
+ if not hashed:
+ self.config.passlib.encrypt(password)
+
self.get_cursor().execute('''
update users set password=%s where name=%s
''', (password, username))
diff --git a/webui.py b/webui.py
index f9e1560..0439393 100644
--- a/webui.py
+++ b/webui.py
@@ -505,38 +505,6 @@ class WebUI:
if script_name == '/id':
return self.run_id()
- # see if the user has provided a username/password
- auth = self.env.get('HTTP_CGI_AUTHORIZATION', '').strip()
- if auth:
- authtype, auth = auth.split(None, 1) # OAuth has many more parameters
- if authtype.lower() == 'basic':
- try:
- un, pw = base64.decodestring(auth).split(':')
- except (binascii.Error, ValueError):
- # Invalid base64, or not exactly one colon
- un = pw = ''
- if self.store.has_user(un):
- pw = hashlib.sha1(pw).hexdigest()
- user = self.store.get_user(un)
- if pw != user['password']:
- raise Unauthorised, 'Incorrect password'
- self.username = un
- self.authenticated = True
- last_login = user['last_login']
- # Only update last_login every minute
- update_last_login = not last_login or (time.time()-time.mktime(last_login.timetuple()) > 60)
- self.store.set_user(un, self.remote_addr, update_last_login)
- else:
- un = self.env.get('SSH_USER', '')
- if un and self.store.has_user(un):
- user = self.store.get_user(un)
- self.username = un
- self.authenticated = self.loggedin = True
- last_login = user['last_login']
- # Only update last_login every minute
- update_last_login = not last_login or (time.time()-time.mktime(last_login.timetuple()) > 60)
- self.store.set_user(un, self.remote_addr, update_last_login)
-
# on logout, we set the cookie to "logged_out"
self.cookie = Cookie.SimpleCookie(self.env.get('HTTP_COOKIE', ''))
try:
@@ -552,6 +520,52 @@ class WebUI:
# no login time update, since looking for the
# cookie did that already
self.store.set_user(name, self.remote_addr, False)
+ else:
+ # see if the user has provided a username/password
+ auth = self.env.get('HTTP_CGI_AUTHORIZATION', '').strip()
+ if auth:
+ authtype, auth = auth.split(None, 1) # OAuth has many more parameters
+ if authtype.lower() == 'basic':
+ try:
+ un, pw = base64.decodestring(auth).split(':')
+ except (binascii.Error, ValueError):
+ # Invalid base64, or not exactly one colon
+ un = pw = ''
+ if self.store.has_user(un):
+ # Fetch the user from the database
+ user = self.store.get_user(un)
+
+ # Verify the hash, and see if it needs migrated
+ ok, new_hash = self.config.passlib.verify_and_update(pw, user["password"])
+
+ # If our password didn't verify as ok then raise an
+ # error.
+ if not ok:
+ raise Unauthorised, 'Incorrect password'
+
+ if new_hash:
+ # The new hash needs to be stored for this user.
+ self.store.setpasswd(un, new_hash, hashed=True)
+
+ # Login the user
+ self.username = un
+ self.authenticated = True
+
+ # Determine if we need to store the users last login,
+ # as we only want to do this once a minute.
+ last_login = user['last_login']
+ update_last_login = not last_login or (time.time()-time.mktime(last_login.timetuple()) > 60)
+ self.store.set_user(un, self.remote_addr, update_last_login)
+ else:
+ un = self.env.get('SSH_USER', '')
+ if un and self.store.has_user(un):
+ user = self.store.get_user(un)
+ self.username = un
+ self.authenticated = self.loggedin = True
+ last_login = user['last_login']
+ # Only update last_login every minute
+ update_last_login = not last_login or (time.time()-time.mktime(last_login.timetuple()) > 60)
+ self.store.set_user(un, self.remote_addr, update_last_login)
# Commit all user-related changes made up to here
if self.username: