Repository: rucio/rucio

Issue:
Userpass authentication not backwards compatible nor unicode capable
Motivation
----------
The current implementation for userpass authentication results in two different hashes for Python 2 and Python 3. The following code lines are affected (and can be found in add_identity as well):

https://github.com/rucio/rucio/blob/7ea877bd8b226012fe2441611aaf82b35c9df82b/lib/rucio/core/authentication.py#L97-L101

Running on Python 2:
```
db_salt = b'as\xab as'  # salt from the database is a bytes object: Column(LargeBinary(255))
password = 'asdasd'
salted_password = '%s%s' % (db_salt, password)
```
Results in `salted_password = 'as\xab asasdasd'` and the hash `'13a429a9643f23f5e0b5ec9cbad8da39d30d2939b391379b9b6763a65a8d0d80'`

In Python 3 (db_salt and password from above):
```
db_salt = b64encode(db_salt).decode()
salted_password = ('%s%s' % (db_salt, password)).encode()
```
Results in `salted_password = 'YXOrIGFzasdasd'` and the hash `'82e5c392dd4dccef16c64c6a58c6059a5cf34c6311e379b342205f35616b958f'`

which means that passwords already hashed on Python 2 and stored in the database are not compatible with using a Python 3 server.

Modification
------------
Instead of unnecessary differentiating between Python 2 and Python 3, bytes should be used directly:
`salted_password = db_salt + password.encode()`


Discussion:

Resolving patch:
diff --git a/lib/rucio/core/authentication.py b/lib/rucio/core/authentication.py
--- a/lib/rucio/core/authentication.py
+++ b/lib/rucio/core/authentication.py
@@ -33,10 +33,9 @@
 import random
 import sys
 import traceback
-from base64 import b64decode, b64encode
+from base64 import b64decode
 
 import paramiko
-import six
 from dogpile.cache import make_region
 from dogpile.cache.api import NO_VALUE
 from sqlalchemy import and_, or_
@@ -94,12 +93,7 @@ def get_auth_token_user_pass(account, username, password, appid, ip=None, sessio
     db_salt = result['salt']
     db_password = result['password']
 
-    if six.PY3:
-        db_salt = b64encode(db_salt).decode()
-        salted_password = ('%s%s' % (db_salt, password)).encode()
-    else:
-        salted_password = '%s%s' % (db_salt, password)
-
+    salted_password = db_salt + password.encode()
     if db_password != hashlib.sha256(salted_password).hexdigest():
         return None
 
diff --git a/lib/rucio/core/identity.py b/lib/rucio/core/identity.py
--- a/lib/rucio/core/identity.py
+++ b/lib/rucio/core/identity.py
@@ -1,31 +1,38 @@
-# Copyright European Organization for Nuclear Research (CERN)
+# -*- coding: utf-8 -*-
+# Copyright 2012-2020 CERN
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
-# You may not use this file except in compliance with the License.
+# you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 #
 # Authors:
-# - Mario Lassnig, <mario.lassnig@cern.ch>, 2012-2015, 2017
-# - Vincent Garonne, <vincent.garonne@cern.ch>, 2012-2013
-# - Thomas Beermann, <thomas.beermann@cern.ch>, 2014
-# - Hannes Hansen, <hannes.jakob.hansen@cern.ch>, 2019
+# - Mario Lassnig <mario.lassnig@cern.ch>, 2012-2017
+# - Vincent Garonne <vincent.garonne@cern.ch>, 2012-2015
+# - Thomas Beermann <thomas.beermann@cern.ch>, 2014
+# - Martin Barisits <martin.barisits@cern.ch>, 2017
+# - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
 # - Ruturaj Gujar <ruturaj.gujar23@gmail.com>, 2019
 # - Jaroslav Guenther <jaroslav.guenther@cern.ch>, 2019
-# - Eli Chadwick, <eli.chadwick@stfc.ac.uk>, 2020
+# - Eli Chadwick <eli.chadwick@stfc.ac.uk>, 2020
+# - Eric Vaandering <ewv@fnal.gov>, 2020
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
 #
 # PY3K COMPATIBLE
 
 import hashlib
 import os
-
-import six
-
-from base64 import b64encode
 from re import match
 
-from sqlalchemy.exc import IntegrityError
 from sqlalchemy import asc
+from sqlalchemy.exc import IntegrityError
 
 from rucio.common import exception
 from rucio.core.account import account_exists
@@ -52,13 +59,9 @@ def add_identity(identity, type, email, password=None, session=None):
     new_id = models.Identity()
     new_id.update({'identity': identity, 'identity_type': type, 'email': email})
 
-    if type == IdentityType.USERPASS and password is not None:
+    if type == IdentityType.USERPASS:
         salt = os.urandom(255)  # make sure the salt has the length of the hash
-        if six.PY3:
-            decoded_salt = b64encode(salt).decode()
-            salted_password = ('%s%s' % (decoded_salt, password)).encode()
-        else:
-            salted_password = '%s%s' % (salt, str(password))
+        salted_password = salt + password.encode()
         password = hashlib.sha256(salted_password).hexdigest()  # hash it
         new_id.update({'salt': salt, 'password': password, 'email': email})
     try:
diff --git a/lib/rucio/db/sqla/util.py b/lib/rucio/db/sqla/util.py
--- a/lib/rucio/db/sqla/util.py
+++ b/lib/rucio/db/sqla/util.py
@@ -1,4 +1,5 @@
-# Copyright 2015-2020 CERN for the benefit of the ATLAS collaboration.
+# -*- coding: utf-8 -*-
+# Copyright 2015-2020 CERN
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,18 +14,21 @@
 # limitations under the License.
 #
 # Authors:
-# - Vincent Garonne <vgaronne@gmail.com>, 2015-2016
-# - Martin Barisits <martin.barisits@cern.ch>, 2017
-# - Mario Lassnig <mario@lassnig.net>, 2018-2019
+# - Vincent Garonne <vincent.garonne@cern.ch>, 2015-2016
+# - Martin Barisits <martin.barisits@cern.ch>, 2017-2019
+# - Mario Lassnig <mario.lassnig@cern.ch>, 2018-2019
 # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2019
 # - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
 # - Ruturaj Gujar <ruturaj.gujar23@gmail.com>, 2019
+# - Eli Chadwick <eli.chadwick@stfc.ac.uk>, 2020
+# - Eric Vaandering <ewv@fnal.gov>, 2020
 # - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
 #
 # PY3K COMPATIBLE
 
 from __future__ import print_function
-from base64 import b64encode
+
 from datetime import datetime
 from hashlib import sha256
 from os import urandom
@@ -32,9 +36,6 @@
 
 from alembic import command
 from alembic.config import Config
-
-from six import PY3
-
 from sqlalchemy import func
 from sqlalchemy.engine import reflection
 from sqlalchemy.exc import IntegrityError
@@ -190,11 +191,7 @@ def create_root_account(create_counters=True):
     account = models.Account(account=InternalAccount(access, 'def'), account_type=AccountType.SERVICE, status=AccountStatus.ACTIVE)
 
     salt = urandom(255)
-    if PY3:
-        decoded_salt = b64encode(salt).decode()
-        salted_password = ('%s%s' % (decoded_salt, up_pwd)).encode()
-    else:
-        salted_password = '%s%s' % (salt, str(up_pwd))
+    salted_password = salt + up_pwd.encode()
     hashed_password = sha256(salted_password).hexdigest()
     identity1 = models.Identity(identity=up_id, identity_type=IdentityType.USERPASS, password=hashed_password, salt=salt, email=up_email)
     iaa1 = models.IdentityAccountAssociation(identity=identity1.identity, identity_type=identity1.identity_type, account=account.account, is_default=True)


Test patch: