summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gluster/swift/common/middleware/gswauth/swauth/authtypes.py77
-rw-r--r--gluster/swift/common/middleware/gswauth/swauth/middleware.py13
-rw-r--r--test/unit/common/middleware/gswauth/swauth/test_middleware.py54
3 files changed, 136 insertions, 8 deletions
diff --git a/gluster/swift/common/middleware/gswauth/swauth/authtypes.py b/gluster/swift/common/middleware/gswauth/swauth/authtypes.py
index 90aad72..fbf532d 100644
--- a/gluster/swift/common/middleware/gswauth/swauth/authtypes.py
+++ b/gluster/swift/common/middleware/gswauth/swauth/authtypes.py
@@ -35,6 +35,7 @@ value or to a default value.
"""
import hashlib
+import os
#: Maximum length any valid token should ever be.
@@ -80,6 +81,20 @@ class Sha1(object):
must be capitalized. encode and match methods must be provided and are
the only ones that will be used by swauth.
"""
+
+ def encode_w_salt(self, salt, key):
+ """
+ Encodes a user key with salt into a particular format. The result of
+ this method will be used internally.
+
+ :param salt: Salt for hashing
+ :param key: User's secret key
+ :returns: A string representing user credentials
+ """
+ enc_key = '%s%s' % (salt, key)
+ enc_val = hashlib.sha1(enc_key).hexdigest()
+ return "sha1:%s$%s" % (salt, enc_val)
+
def encode(self, key):
"""
Encodes a user key into a particular format. The result of this method
@@ -88,9 +103,8 @@ class Sha1(object):
:param key: User's secret key
:returns: A string representing user credentials
"""
- enc_key = '%s%s' % (self.salt, key)
- enc_val = hashlib.sha1(enc_key).hexdigest()
- return "sha1:%s$%s" % (self.salt, enc_val)
+ salt = self.salt or os.urandom(32).encode('base64').rstrip()
+ return self.encode_w_salt(salt, key)
def match(self, key, creds):
"""
@@ -100,4 +114,59 @@ class Sha1(object):
:param creds: User's stored credentials
:returns: True if the supplied key is valid, False otherwise
"""
- return self.encode(key) == creds
+
+ type, rest = creds.split(':')
+ salt, enc = rest.split('$')
+
+ return self.encode_w_salt(salt, key) == creds
+
+
+class Sha512(object):
+ """
+ Provides a particular auth type for encoding format for encoding and
+ matching user keys.
+
+ This class must be all lowercase except for the first character, which
+ must be capitalized. encode and match methods must be provided and are
+ the only ones that will be used by swauth.
+ """
+
+ def encode_w_salt(self, salt, key):
+ """
+ Encodes a user key with salt into a particular format. The result of
+ this method will be used internal.
+
+ :param salt: Salt for hashing
+ :param key: User's secret key
+ :returns: A string representing user credentials
+ """
+ enc_key = '%s%s' % (salt, key)
+ enc_val = hashlib.sha512(enc_key).hexdigest()
+ return "sha512:%s$%s" % (salt, enc_val)
+
+ def encode(self, key):
+ """
+ Encodes a user key into a particular format. The result of this method
+ will be used by swauth for storing user credentials.
+
+ If salt is not manually set in conf file, a random salt will be
+ generated and used.
+
+ :param key: User's secret key
+ :returns: A string representing user credentials
+ """
+ salt = self.salt or os.urandom(32).encode('base64').rstrip()
+ return self.encode_w_salt(salt, key)
+
+ def match(self, key, creds):
+ """Checks whether the user-provided key matches the user's credentials
+
+ :param key: User-supplied key
+ :param creds: User's stored credentials
+ :returns: True if the supplied key is valid, False otherwise
+ """
+
+ type, rest = creds.split(':')
+ salt, enc = rest.split('$')
+
+ return self.encode_w_salt(salt, key) == creds
diff --git a/gluster/swift/common/middleware/gswauth/swauth/middleware.py b/gluster/swift/common/middleware/gswauth/swauth/middleware.py
index e181ece..745c6f1 100644
--- a/gluster/swift/common/middleware/gswauth/swauth/middleware.py
+++ b/gluster/swift/common/middleware/gswauth/swauth/middleware.py
@@ -1475,14 +1475,21 @@ class Swauth(object):
def credentials_match(self, user_detail, key):
"""
Returns True if the key is valid for the user_detail.
- It will use self.auth_encoder to check for a key match.
+ It will use auth_encoder type the password was encoded with,
+ to check for a key match.
:param user_detail: The dict for the user.
:param key: The key to validate for the user.
:returns: True if the key is valid for the user, False if not.
"""
- return user_detail and self.auth_encoder().match(
- key, user_detail.get('auth'))
+ if user_detail:
+ creds = user_detail.get('auth')
+ auth_type = creds.split(':')[0]
+ auth_encoder = getattr(authtypes, auth_type.title(), None)
+ if auth_encoder is None:
+ self.logger.error('Invalid auth_type %s' % auth_type)
+ return False
+ return user_detail and auth_encoder().match(key, creds)
def is_user_changing_own_key(self, req, user):
"""
diff --git a/test/unit/common/middleware/gswauth/swauth/test_middleware.py b/test/unit/common/middleware/gswauth/swauth/test_middleware.py
index 67abbe4..2d30082 100644
--- a/test/unit/common/middleware/gswauth/swauth/test_middleware.py
+++ b/test/unit/common/middleware/gswauth/swauth/test_middleware.py
@@ -18,8 +18,8 @@ try:
except ImportError:
import json
import unittest
-from nose import SkipTest
from contextlib import contextmanager
+import mock
from time import time
from swift.common.swob import Request, Response
@@ -126,6 +126,33 @@ class TestAuth(unittest.TestCase):
'token_life': str(DEFAULT_TOKEN_LIFE),
'max_token_life': str(MAX_TOKEN_LIFE)})(FakeApp())
+ def test_salt(self):
+ for auth_type in ('sha1', 'sha512'):
+ # Salt not manually set (gswauthsalt should be default)
+ test_auth = \
+ auth.filter_factory({
+ 'super_admin_key': 'supertest',
+ 'token_life': str(DEFAULT_TOKEN_LIFE),
+ 'max_token_life': str(MAX_TOKEN_LIFE),
+ 'auth_type': auth_type})(FakeApp())
+ self.assertEqual(test_auth.auth_encoder.salt, "gswauthsalt")
+ h_key = test_auth.auth_encoder().encode("key")
+ prefix = auth_type + ":" + "gswauthsalt" + '$'
+ self.assertTrue(h_key.startswith(prefix))
+
+ # Salt manually set
+ test_auth = \
+ auth.filter_factory({
+ 'super_admin_key': 'supertest',
+ 'token_life': str(DEFAULT_TOKEN_LIFE),
+ 'max_token_life': str(MAX_TOKEN_LIFE),
+ 'auth_type': auth_type,
+ 'auth_type_salt': "mysalt"})(FakeApp())
+ self.assertEqual(test_auth.auth_encoder.salt, "mysalt")
+ h_key = test_auth.auth_encoder().encode("key")
+ prefix = auth_type + ":" + "mysalt" + '$'
+ self.assertTrue(h_key.startswith(prefix))
+
def test_super_admin_key_not_required(self):
auth.filter_factory({})(FakeApp())
@@ -237,6 +264,31 @@ class TestAuth(unittest.TestCase):
ath.dsc_url2,
'http://host2/path2')
+ def test_credentials_match_auth_encoder_type(self):
+ plaintext_auth = {'auth': 'plaintext:key'}
+ sha1_key = ("sha1:T0YFdhqN4uDRWiYLxWa7H2T8AewG4fEYQyJFRLsgcfk=$46c58"
+ "07eb8a32e8f404fea9eaaeb60b7e1207ff1")
+ sha1_auth = {'auth': sha1_key}
+ sha512_key = ("sha512:aSm0jEeqIp46T5YLZy1r8+cXs/Xzs1S4VUwVauhBs44=$ef"
+ "7332ec1288bf69c75682eb8d459d5a84baa7e43f45949c242a9af9"
+ "7130ef16ac361fe1aa33a789e218122b83c54ef1923fc015080741"
+ "ca21f6187329f6cb7a")
+ sha512_auth = {'auth': sha512_key}
+
+ # test all possible config settings work with all possible auth types
+ for auth_type in ('plaintext', 'sha1', 'sha512'):
+ test_auth = auth.filter_factory({'super_admin_key': 'superkey',
+ 'auth_type': auth_type})(FakeApp())
+ for detail in (plaintext_auth, sha1_auth, sha512_auth):
+ self.assertTrue(test_auth.credentials_match(detail, 'key'))
+ # test invalid auth type stored
+ invalid_detail = {'auth': 'Junk:key'}
+ test_auth.logger = mock.Mock()
+ self.assertFalse(test_auth.credentials_match(invalid_detail,
+ 'key'))
+ # make sure error is logged
+ test_auth.logger.called_once_with('Invalid auth_type Junk')
+
def test_top_level_denied(self):
resp = Request.blank(
'/').get_response(self.test_auth)