From d5770ee21401468c49391eee3e8e8a724e92425f Mon Sep 17 00:00:00 2001
From: Clay Gerrard <clay.gerrard@rackspace.com>
Date: Thu, 16 Sep 2010 16:44:44 -0500
Subject: [PATCH] trying to make sense of auth middleware and reseller prefix

---
 doc/source/development_auth.rst |   7 +-
 swift/auth/server.py            |   4 +-
 swift/common/middleware/auth.py | 109 ++++++++++++++++++--------------
 test/unit/auth/test_server.py   |   3 +-
 4 files changed, 70 insertions(+), 53 deletions(-)

diff --git a/doc/source/development_auth.rst b/doc/source/development_auth.rst
index 410312f3db..afbe776bab 100644
--- a/doc/source/development_auth.rst
+++ b/doc/source/development_auth.rst
@@ -60,9 +60,10 @@ Example Authentication with DevAuth:
     * The external DevAuth server responds with "X-Auth-Groups:
       test:tester,test,AUTH_storage_xyz"
     * Now this user will have full access (via authorization procedures later)
-      to the AUTH_storage_xyz Swift storage account and access to other storage
-      accounts with the same `AUTH_` reseller prefix and has an ACL specifying
-      at least one of those three groups returned.
+      to the AUTH_storage_xyz Swift storage account and access to containers in
+      other storage accounts, provided the storage account begins with the same
+      `AUTH_` reseller prefix and the container has an ACL specifying at least
+      one of those three groups returned.
 
 Authorization is performed through callbacks by the Swift Proxy server to the
 WSGI environment's swift.authorize value, if one is set. The swift.authorize
diff --git a/swift/auth/server.py b/swift/auth/server.py
index e8214db354..24acda6196 100644
--- a/swift/auth/server.py
+++ b/swift/auth/server.py
@@ -454,7 +454,7 @@ YOU HAVE A FEW OPTIONS:
         if create_reseller_admin and (
               request.headers.get('X-Auth-Admin-User') != '.super_admin' or
               request.headers.get('X-Auth-Admin-Key') != self.super_admin_key):
-            return HTTPForbidden(request=request)
+            return HTTPUnauthorized(request=request)
         create_account_admin = \
             request.headers.get('x-auth-user-admin') == 'true'
         if create_account_admin and \
@@ -484,7 +484,7 @@ YOU HAVE A FEW OPTIONS:
         """
         if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \
                request.headers.get('X-Auth-Admin-Key') != self.super_admin_key:
-            return HTTPForbidden(request=request)
+            return HTTPUnauthorized(request=request)
         result = self.recreate_accounts()
         return Response(result, 200, request=request)
 
diff --git a/swift/common/middleware/auth.py b/swift/common/middleware/auth.py
index f5e7a2c9d0..05939d06ac 100644
--- a/swift/common/middleware/auth.py
+++ b/swift/common/middleware/auth.py
@@ -20,7 +20,7 @@ from webob.exc import HTTPForbidden, HTTPUnauthorized
 
 from swift.common.bufferedhttp import http_connect_raw as http_connect
 from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
-from swift.common.utils import cache_from_env, split_path
+from swift.common.utils import cache_from_env, split_path, TRUE_VALUES
 
 
 class DevAuth(object):
@@ -35,9 +35,33 @@ class DevAuth(object):
         self.auth_host = conf.get('ip', '127.0.0.1')
         self.auth_port = int(conf.get('port', 11000))
         self.ssl = \
-            conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
+            conf.get('ssl', 'false').lower() in TRUE_VALUES
         self.timeout = int(conf.get('node_timeout', 10))
 
+    def get_groups(self, token):
+        memcache_client = cache_from_env(env)
+        key = '%s/token/%s' % (self.reseller_prefix, token)
+        cached_auth_data = memcache_client.get(key)
+        if cached_auth_data:
+            start, expiration, groups = cached_auth_data
+            if time() - start > expiration:
+                groups = None
+        if not groups:
+            with Timeout(self.timeout):
+                conn = http_connect(self.auth_host, self.auth_port, 'GET',
+                                    '/token/%s' % token, ssl=self.ssl)
+                resp = conn.getresponse()
+                resp.read()
+                conn.close()
+            if resp.status // 100 != 2:
+                return None
+
+            expiration = float(resp.getheader('x-auth-ttl'))
+            groups = resp.getheader('x-auth-groups')
+            memcache_client.set(key, (time(), expiration, groups),
+                                timeout=expiration)
+            return groups
+
     def __call__(self, env, start_response):
         """
         Accepts a standard WSGI application call, authenticating the request
@@ -45,57 +69,48 @@ class DevAuth(object):
         validation. For an authenticated request, REMOTE_USER will be set to a
         comma separated list of the user's groups.
         """
+
         token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
-        if token and token.startswith(self.reseller_prefix):
-            groups = None
-            memcache_client = cache_from_env(env)
-            key = '%s/token/%s' % (self.reseller_prefix, token)
-            cached_auth_data = memcache_client.get(key)
-            if cached_auth_data:
-                start, expiration, groups = cached_auth_data
-                if time() - start > expiration:
-                    groups = None
-            if not groups:
-                with Timeout(self.timeout):
-                    conn = http_connect(self.auth_host, self.auth_port, 'GET',
-                                        '/token/%s' % token, ssl=self.ssl)
-                    resp = conn.getresponse()
-                    resp.read()
-                    conn.close()
-                if resp.status // 100 != 2:
-                    if self.reseller_prefix:
-                        return HTTPUnauthorized()(env, start_response)
-                    else:
-                        # If we have no reseller prefix, we can't deny the
-                        # request just yet because another auth middleware
-                        # might be able to approve.
-                        if 'swift.authorize' not in env:
-                            env['swift.authorize'] = self.denied_response
-                        return self.app(env, start_response)
-                expiration = float(resp.getheader('x-auth-ttl'))
-                groups = resp.getheader('x-auth-groups')
-                memcache_client.set(key, (time(), expiration, groups),
-                                    timeout=expiration)
-            env['REMOTE_USER'] = groups
+
+        if not self.reseller_prefix:
+            # all requests belong to me
+            if token:
+                # I should attempt to auth any token
+                groups = self.get_groups(token)
+            else:
+                groups = None # no token is same as an unauthorized token
+            if groups:
+                env['REMOTE_USER'] = groups
+                user = groups and groups.split(',', 1)[0] or ''
+                env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token)
             env['swift.authorize'] = self.authorize
             env['swift.clean_acl'] = clean_acl
-            # We know the proxy logs the token, so we augment it just a bit to
-            # also log the authenticated user.
-            user = groups and groups.split(',', 1)[0] or ''
-            env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token)
         else:
-            version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True)
-            if rest and rest.startswith(self.reseller_prefix):
-                # If we don't have a reseller prefix we have no way of knowing
-                # if we should be handling the request, so we only set
-                # swift.authorize if it isn't set already (or we have a
-                # reseller prefix that matches so we know we should handle the
-                # request).
-                if self.reseller_prefix or 'swift.authorize' not in env:
+            # as a reseller, I must respect that just can my auth can't provide
+            # groups for a token, others may
+            if token and token.startswith(self.reseller_prefix)::
+                # attempt to auth my token with my auth server
+                groups = self.get_groups(token)
+                if groups:
+                    # authenticated!
+                    env['REMOTE_USER'] = groups
+                    user = groups and groups.split(',', 1)[0] or ''
+                    env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token)
+                env['swift.authorize'] = self.authorize
+                env['swift.clean_acl'] = clean_acl
+            else:
+                # I can't claim this token, but I might claim the annoynomous request
+                version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True)
+                if rest and rest.startswith(self.reseller_prefix):
+                    # annoynomous access to my reseller's accounts
                     env['swift.authorize'] = self.authorize
                     env['swift.clean_acl'] = clean_acl
-            elif 'swift.authorize' not in env:
-                env['swift.authorize'] = self.denied_response
+                else:
+                    # not my token, not my account
+                    # good idea regardless...
+                    if 'swift.authorize' not in env:
+                        env['swift.authorize'] = self.denied_response
+
         return self.app(env, start_response)
 
     def authorize(self, req):
diff --git a/test/unit/auth/test_server.py b/test/unit/auth/test_server.py
index 6ab8b76095..d63f843abe 100644
--- a/test/unit/auth/test_server.py
+++ b/test/unit/auth/test_server.py
@@ -685,7 +685,8 @@ class TestAuthServer(unittest.TestCase):
         conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
         self.assertRaises(ValueError, auth_server.AuthController, conf)
         conf['super_admin_key'] = 'testkey'
-        auth_server.AuthController(conf)
+        controller = auth_server.AuthController(conf)
+        self.assertEquals(controller.super_admin_key, conf['super_admin_key'])
 
     def test_add_storage_account(self):
         auth_server.http_connect = fake_http_connect(201)