Add support for setting already hashed password

You can use this for restoring dumped users list from swauth-list
command.
Change-Id: Ia77d7a0b91b2f79999286858e383477a80d7db15
This commit is contained in:
Ondřej Nový 2016-05-23 17:47:07 +02:00
parent 9b27778987
commit b548d3dcf7
4 changed files with 77 additions and 4 deletions

View File

@ -43,6 +43,9 @@ if __name__ == '__main__':
'the account already exists, this will have no effect on existing ' 'the account already exists, this will have no effect on existing '
'service URLs. Those will need to be updated with ' 'service URLs. Those will need to be updated with '
'swauth-set-account-service') 'swauth-set-account-service')
parser.add_option('-e', '--hashed', dest='password_hashed',
action='store_true', default=False, help='Supplied password is '
'already hashed and in format <auth_type>:<hashed_password>')
parser.add_option('-A', '--admin-url', dest='admin_url', parser.add_option('-A', '--admin-url', dest='admin_url',
default='http://127.0.0.1:8080/auth/', help='The URL to the auth ' default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
'subsystem (default: http://127.0.0.1:8080/auth/') 'subsystem (default: http://127.0.0.1:8080/auth/')
@ -91,12 +94,15 @@ if __name__ == '__main__':
path = '%sv2/%s/%s' % (parsed_path, account, user) path = '%sv2/%s/%s' % (parsed_path, account, user)
headers = {'X-Auth-Admin-User': options.admin_user, headers = {'X-Auth-Admin-User': options.admin_user,
'X-Auth-Admin-Key': options.admin_key, 'X-Auth-Admin-Key': options.admin_key,
'X-Auth-User-Key': password,
'Content-Length': '0'} 'Content-Length': '0'}
if options.admin: if options.admin:
headers['X-Auth-User-Admin'] = 'true' headers['X-Auth-User-Admin'] = 'true'
if options.reseller_admin: if options.reseller_admin:
headers['X-Auth-User-Reseller-Admin'] = 'true' headers['X-Auth-User-Reseller-Admin'] = 'true'
if options.password_hashed:
headers['X-Auth-User-Key-Hash'] = password
else:
headers['X-Auth-User-Key'] = password
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers, conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
ssl=(parsed.scheme == 'https')) ssl=(parsed.scheme == 'https'))
resp = conn.getresponse() resp = conn.getresponse()

View File

@ -287,7 +287,9 @@ A user can be created with a PUT request against a non-existent
user URI. The new user's password must be set using the user URI. The new user's password must be set using the
``X-Auth-User-Key`` header. The user name MUST NOT start with a ``X-Auth-User-Key`` header. The user name MUST NOT start with a
period ('.'). This requirement is enforced by the API, and will period ('.'). This requirement is enforced by the API, and will
result in a 400 error. result in a 400 error. Alternatively you can use
``X-Auth-User-Key-Hash`` header for providing already hashed
password in format ``<auth_type>:<hashed_password>``.
Optional Headers: Optional Headers:

View File

@ -1038,6 +1038,8 @@ class Swauth(object):
account. account.
X-Auth-User-Key represents the user's key (url encoded), X-Auth-User-Key represents the user's key (url encoded),
- OR -
X-Auth-User-Key-Hash represents the user's hashed key (url encoded),
X-Auth-User-Admin may be set to `true` to create an account .admin, and X-Auth-User-Admin may be set to `true` to create an account .admin, and
X-Auth-User-Reseller-Admin may be set to `true` to create a X-Auth-User-Reseller-Admin may be set to `true` to create a
.reseller_admin. .reseller_admin.
@ -1062,14 +1064,22 @@ class Swauth(object):
account = req.path_info_pop() account = req.path_info_pop()
user = req.path_info_pop() user = req.path_info_pop()
key = unquote(req.headers.get('x-auth-user-key', '')) key = unquote(req.headers.get('x-auth-user-key', ''))
key_hash = unquote(req.headers.get('x-auth-user-key-hash', ''))
admin = req.headers.get('x-auth-user-admin') == 'true' admin = req.headers.get('x-auth-user-admin') == 'true'
reseller_admin = \ reseller_admin = \
req.headers.get('x-auth-user-reseller-admin') == 'true' req.headers.get('x-auth-user-reseller-admin') == 'true'
if reseller_admin: if reseller_admin:
admin = True admin = True
if req.path_info or not account or account[0] == '.' or not user or \ if req.path_info or not account or account[0] == '.' or not user or \
user[0] == '.' or not key: user[0] == '.' or (not key and not key_hash):
return HTTPBadRequest(request=req) return HTTPBadRequest(request=req)
if key_hash:
if ':' not in key_hash:
return HTTPBadRequest(request=req)
auth_type, hash = key_hash.split(':')
if getattr(swauth.authtypes, auth_type.title(), None) is None:
return HTTPBadRequest(request=req)
user_arg = account + ':' + user user_arg = account + ':' + user
if reseller_admin: if reseller_admin:
if not self.is_super_admin(req) and\ if not self.is_super_admin(req) and\
@ -1095,7 +1105,7 @@ class Swauth(object):
groups.append('.admin') groups.append('.admin')
if reseller_admin: if reseller_admin:
groups.append('.reseller_admin') groups.append('.reseller_admin')
auth_value = self.auth_encoder().encode(key) auth_value = key_hash or self.auth_encoder().encode(key)
resp = self.make_pre_authed_request( resp = self.make_pre_authed_request(
req.environ, 'PUT', path, req.environ, 'PUT', path,
json.dumps({'auth': auth_value, json.dumps({'auth': auth_value,

View File

@ -18,6 +18,7 @@ import json
import mock import mock
from time import time from time import time
import unittest import unittest
from urllib import quote
from swift.common.swob import Request from swift.common.swob import Request
from swift.common.swob import Response from swift.common.swob import Response
@ -3025,6 +3026,60 @@ class TestAuth(unittest.TestCase):
self.assertEqual(resp.status_int, 500) self.assertEqual(resp.status_int, 500)
self.assertEqual(self.test_auth.app.calls, 1) self.assertEqual(self.test_auth.app.calls, 1)
def test_put_user_key_hash(self):
key_hash = ("sha512:aSm0jEeqIp46T5YLZy1r8+cXs/Xzs1S4VUwVauhBs44=$ef"
"7332ec1288bf69c75682eb8d459d5a84baa7e43f45949c242a9af9"
"7130ef16ac361fe1aa33a789e218122b83c54ef1923fc015080741"
"ca21f6187329f6cb7a")
self.test_auth.app = FakeApp(iter([
('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
# PUT of user object
('201 Created', {}, '')]))
resp = Request.blank('/auth/v2/act/usr',
environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Auth-Admin-User': '.super_admin',
'X-Auth-Admin-Key': 'supertest',
'X-Auth-User-Key-Hash': quote(key_hash)}
).get_response(self.test_auth)
self.assertEqual(resp.status_int, 201)
self.assertEqual(self.test_auth.app.calls, 2)
self.assertEqual(json.loads(self.test_auth.app.request.body),
{"groups": [{"name": "act:usr"}, {"name": "act"}],
"auth": key_hash})
def test_put_user_key_hash_wrong_type(self):
key_hash = "wrong_auth_type:1234"
self.test_auth.app = FakeApp(iter([
('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
# PUT of user object
('201 Created', {}, '')]))
resp = Request.blank('/auth/v2/act/usr',
environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Auth-Admin-User': '.super_admin',
'X-Auth-Admin-Key': 'supertest',
'X-Auth-User-Key-Hash': quote(key_hash)}
).get_response(self.test_auth)
self.assertEqual(resp.status_int, 400)
self.assertEqual(self.test_auth.app.calls, 0)
def test_put_user_key_hash_wrong_format(self):
key_hash = "1234"
self.test_auth.app = FakeApp(iter([
('200 Ok', {'X-Container-Meta-Account-Id': 'AUTH_cfa'}, ''),
# PUT of user object
('201 Created', {}, '')]))
resp = Request.blank('/auth/v2/act/usr',
environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Auth-Admin-User': '.super_admin',
'X-Auth-Admin-Key': 'supertest',
'X-Auth-User-Key-Hash': quote(key_hash)}
).get_response(self.test_auth)
self.assertEqual(resp.status_int, 400)
self.assertEqual(self.test_auth.app.calls, 0)
def test_delete_user_bad_creds(self): def test_delete_user_bad_creds(self):
self.test_auth.app = FakeApp(iter([ self.test_auth.app = FakeApp(iter([
('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"}, ('200 Ok', {}, json.dumps({"groups": [{"name": "act2:adm"},