Commits

Donald Stufft committed 43cad40

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

  • Participants
  • Parent commits fc588bc
  • Branches passlib

Comments (0)

Files changed (4)

config.ini.template

 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 =
 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.
     '''
 
         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:]
         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))
         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,
                     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))
         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:
             # 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: