diff options
author | Donald Stufft <donald@stufft.io> | 2013-02-12 00:43:17 -0500 |
---|---|---|
committer | Donald Stufft <donald@stufft.io> | 2013-02-12 00:43:17 -0500 |
commit | 622d5b0defc2c08e58a5544c0423cc7d98538cf3 (patch) | |
tree | 6780721d5cef359ccee4ab53a51bda6834cd3d6b | |
parent | 22e57b4405e4a94810d472b89fddad1b3b26f91e (diff) | |
download | decorator-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.template | 5 | ||||
-rw-r--r-- | config.py | 12 | ||||
-rw-r--r-- | store.py | 10 | ||||
-rw-r--r-- | webui.py | 78 |
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 = @@ -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:] @@ -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)) @@ -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: |