summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorboden <brussell@us.ibm.com>2012-10-31 15:34:00 -0400
committerAdam Young <ayoung@redhat.com>2012-10-31 15:34:00 -0400
commitf79f701782fa583380138e1fba702fb00bcac52e (patch)
treec98799d3709c2fcdf679890fa29336f925506f57
parentc53ffe59863a02861c3872fbc3190e7e536222a1 (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.py4
-rw-r--r--keystone/service.py47
-rw-r--r--tests/test_service.py77
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'))