diff options
| author | boden <brussell@us.ibm.com> | 2012-10-31 15:34:00 -0400 |
|---|---|---|
| committer | Adam Young <ayoung@redhat.com> | 2012-10-31 15:34:00 -0400 |
| commit | f79f701782fa583380138e1fba702fb00bcac52e (patch) | |
| tree | c98799d3709c2fcdf679890fa29336f925506f57 | |
| parent | c53ffe59863a02861c3872fbc3190e7e536222a1 (diff) | |
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
| -rw-r--r-- | keystone/common/wsgi.py | 4 | ||||
| -rw-r--r-- | keystone/service.py | 47 | ||||
| -rw-r--r-- | tests/test_service.py | 77 |
3 files changed, 116 insertions, 12 deletions
diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py index 9b42491d..39529bc9 100644 --- a/keystone/common/wsgi.py +++ b/keystone/common/wsgi.py @@ -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 diff --git a/keystone/service.py b/keystone/service.py index 4cc4ad26..b6443a70 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -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): diff --git a/tests/test_service.py b/tests/test_service.py index 979e9472..775b2ca7 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -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')) |
