Implements REMOTE_USER authentication support.
Adds support for non-identity authentication via REMOTE_USER environ context variable thereby permitting external services (paste pipeline, web fronting or other) to authenticate a request. Also fixes a pep8 issue. This change is in support for blueprint pluggable-identity-authentication-handlers Change-Id: Ib0a36b14f135dd87601e3c6d28f7874193d66b34
This commit is contained in:
parent
c53ffe5986
commit
f79f701782
@ -199,6 +199,10 @@ class Application(BaseApplication):
|
||||
context = req.environ.get(CONTEXT_ENV, {})
|
||||
context['query_string'] = dict(req.params.iteritems())
|
||||
params = req.environ.get(PARAMS_ENV, {})
|
||||
if 'REMOTE_USER' in req.environ:
|
||||
context['REMOTE_USER'] = req.environ['REMOTE_USER']
|
||||
elif context.get('REMOTE_USER', None) is not None:
|
||||
del context['REMOTE_USER']
|
||||
params.update(arg_dict)
|
||||
|
||||
# TODO(termie): do some basic normalization on methods
|
||||
|
@ -289,6 +289,18 @@ class TokenController(wsgi.Application):
|
||||
raise exception.ValidationError(attribute='auth',
|
||||
target='request body')
|
||||
|
||||
if auth is None:
|
||||
auth = {}
|
||||
remote_auth = False
|
||||
if 'REMOTE_USER' in context and not 'token' in auth:
|
||||
# authenticated external request
|
||||
remote_auth = True
|
||||
|
||||
if 'passwordCredentials' not in auth:
|
||||
auth['passwordCredentials'] = {}
|
||||
auth['passwordCredentials']['username'] = context.get(
|
||||
'REMOTE_USER', None)
|
||||
|
||||
if 'passwordCredentials' in auth:
|
||||
user_id = auth['passwordCredentials'].get('userId', None)
|
||||
username = auth['passwordCredentials'].get('username', '')
|
||||
@ -300,6 +312,10 @@ class TokenController(wsgi.Application):
|
||||
attribute='username or userId',
|
||||
target='passwordCredentials')
|
||||
|
||||
tenant_ref = None
|
||||
user_ref = None
|
||||
metadata_ref = {}
|
||||
|
||||
if username:
|
||||
try:
|
||||
user_ref = self.identity_api.get_user_by_name(
|
||||
@ -308,7 +324,7 @@ class TokenController(wsgi.Application):
|
||||
except exception.UserNotFound:
|
||||
raise exception.Unauthorized()
|
||||
|
||||
if not password:
|
||||
if not password and not remote_auth:
|
||||
raise exception.ValidationError(
|
||||
attribute='password',
|
||||
target='passwordCredentials')
|
||||
@ -324,11 +340,30 @@ class TokenController(wsgi.Application):
|
||||
raise exception.Unauthorized()
|
||||
|
||||
try:
|
||||
auth_info = self.identity_api.authenticate(context=context,
|
||||
user_id=user_id,
|
||||
password=password,
|
||||
tenant_id=tenant_id)
|
||||
(user_ref, tenant_ref, metadata_ref) = auth_info
|
||||
if not remote_auth:
|
||||
# local identity authentication required
|
||||
auth_info = self.identity_api.authenticate(
|
||||
context=context,
|
||||
user_id=user_id,
|
||||
password=password,
|
||||
tenant_id=tenant_id)
|
||||
(user_ref, tenant_ref, metadata_ref) = auth_info
|
||||
else:
|
||||
# remote authentication already performed
|
||||
if not user_ref:
|
||||
user_ref = self.identity_api.get_user(
|
||||
self.identity_api,
|
||||
user_id)
|
||||
if tenant_id:
|
||||
if not tenant_ref:
|
||||
tenant_ref = self.identity_api.get_tenant(
|
||||
self.identity_api,
|
||||
tenant_id)
|
||||
metadata_ref = self.identity_api.get_metadata(
|
||||
self.identity_api,
|
||||
user_id,
|
||||
tenant_id)
|
||||
auth_info = (user_ref, tenant_ref, metadata_ref)
|
||||
|
||||
# If the user is disabled don't allow them to authenticate
|
||||
if not user_ref.get('enabled', True):
|
||||
|
@ -12,10 +12,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import default_fixtures
|
||||
|
||||
from keystone import exception
|
||||
from keystone import identity
|
||||
from keystone import service
|
||||
from keystone import test
|
||||
from keystone.identity.backends import kvs as kvs_identity
|
||||
|
||||
|
||||
class FakeIdentityManager(object):
|
||||
@ -34,33 +37,95 @@ class TokenControllerTest(test.TestCase):
|
||||
right exception."""
|
||||
body_dict = {'passwordCredentials': {}, 'tenantName': 'demo'}
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, body_dict)
|
||||
{}, body_dict)
|
||||
|
||||
def test_authenticate_no_username(self):
|
||||
"""Verify skipping username raises the right exception."""
|
||||
body_dict = {'passwordCredentials': {'password': 'pass'},
|
||||
'tenantName': 'demo'}
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, body_dict)
|
||||
{}, body_dict)
|
||||
|
||||
def test_authenticate_no_password(self):
|
||||
"""Verify skipping password raises the right exception."""
|
||||
body_dict = {'passwordCredentials': {'username': 'user1'},
|
||||
'tenantName': 'demo'}
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, body_dict)
|
||||
{}, body_dict)
|
||||
|
||||
def test_authenticate_blank_request_body(self):
|
||||
"""Verify sending empty json dict raises the right exception."""
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, {})
|
||||
{}, {})
|
||||
|
||||
def test_authenticate_blank_auth(self):
|
||||
"""Verify sending blank 'auth' raises the right exception."""
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, {'auth': {}})
|
||||
{}, {'auth': {}})
|
||||
|
||||
def test_authenticate_invalid_auth_content(self):
|
||||
"""Verify sending invalid 'auth' raises the right exception."""
|
||||
self.assertRaises(exception.ValidationError, self.api.authenticate,
|
||||
None, {'auth': 'abcd'})
|
||||
{}, {'auth': 'abcd'})
|
||||
|
||||
|
||||
class RemoteUserTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(RemoteUserTest, self).setUp()
|
||||
self.identity_api = kvs_identity.Identity()
|
||||
self.load_fixtures(default_fixtures)
|
||||
self.api = service.TokenController()
|
||||
|
||||
def _build_user_auth(self, username, passwd, tenant):
|
||||
auth_json = {'passwordCredentials': {}}
|
||||
if username is not None:
|
||||
auth_json['passwordCredentials']['username'] = username
|
||||
if passwd is not None:
|
||||
auth_json['passwordCredentials']['password'] = passwd
|
||||
if tenant is not None:
|
||||
auth_json['tenantName'] = tenant
|
||||
return auth_json
|
||||
|
||||
def assertEqualTokens(self, a, b):
|
||||
def normalize(token):
|
||||
token['access']['token']['id'] = 'dummy'
|
||||
# truncate to eliminate timing problems
|
||||
issued = token['access']['token']['issued_at']
|
||||
token['access']['token']['issued_at'] = issued[:-8]
|
||||
# truncate to eliminate timing problems
|
||||
expires = token['access']['token']['expires']
|
||||
token['access']['token']['expires'] = expires[:-3]
|
||||
return token
|
||||
return self.assertDictEqual(normalize(a), normalize(b))
|
||||
|
||||
def test_unscoped_remote_authn(self):
|
||||
local_token = self.api.authenticate(
|
||||
{},
|
||||
self._build_user_auth('FOO', 'foo2', None))
|
||||
remote_token = self.api.authenticate(
|
||||
{'REMOTE_USER': 'FOO'},
|
||||
self._build_user_auth('FOO', 'nosir', None))
|
||||
self.assertEqualTokens(local_token, remote_token)
|
||||
|
||||
def test_unscoped_remote_authn_jsonless(self):
|
||||
self.assertRaises(
|
||||
exception.ValidationError,
|
||||
self.api.authenticate,
|
||||
{'REMOTE_USER': 'FOO'},
|
||||
None)
|
||||
|
||||
def test_scoped_remote_authn(self):
|
||||
local_token = self.api.authenticate(
|
||||
{},
|
||||
self._build_user_auth('FOO', 'foo2', 'BAR'))
|
||||
remote_token = self.api.authenticate(
|
||||
{'REMOTE_USER': 'FOO'},
|
||||
self._build_user_auth('FOO', 'nosir', 'BAR'))
|
||||
self.assertEqualTokens(local_token, remote_token)
|
||||
|
||||
def test_scoped_remote_authn_invalid_user(self):
|
||||
self.assertRaises(
|
||||
exception.Unauthorized,
|
||||
self.api.authenticate,
|
||||
{'REMOTE_USER': 'FOOZBALL'},
|
||||
self._build_user_auth('FOO', 'nosir', 'BAR'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user