Пример #1
0
 def test_split_path(self):
     """ Test chase.common.utils.split_account_path """
     self.assertRaises(ValueError, utils.split_path, '')
     self.assertRaises(ValueError, utils.split_path, '/')
     self.assertRaises(ValueError, utils.split_path, '//')
     self.assertEquals(utils.split_path('/a'), ['a'])
     self.assertRaises(ValueError, utils.split_path, '//a')
     self.assertEquals(utils.split_path('/a/'), ['a'])
     self.assertRaises(ValueError, utils.split_path, '/a/c')
     self.assertRaises(ValueError, utils.split_path, '//c')
     self.assertRaises(ValueError, utils.split_path, '/a/c/')
     self.assertRaises(ValueError, utils.split_path, '/a//')
     self.assertRaises(ValueError, utils.split_path, '/a', 2)
     self.assertRaises(ValueError, utils.split_path, '/a', 2, 3)
     self.assertRaises(ValueError, utils.split_path, '/a', 2, 3, True)
     self.assertEquals(utils.split_path('/a/c', 2), ['a', 'c'])
     self.assertEquals(utils.split_path('/a/c/o', 3), ['a', 'c', 'o'])
     self.assertRaises(ValueError, utils.split_path, '/a/c/o/r', 3, 3)
     self.assertEquals(utils.split_path('/a/c/o/r', 3, 3, True),
                       ['a', 'c', 'o/r'])
     self.assertEquals(utils.split_path('/a/c', 2, 3, True),
                       ['a', 'c', None])
     self.assertRaises(ValueError, utils.split_path, '/a', 5, 4)
     self.assertEquals(utils.split_path('/a/c/', 2), ['a', 'c'])
     self.assertEquals(utils.split_path('/a/c/', 2, 3), ['a', 'c', ''])
     try:
         utils.split_path('o\nn e', 2)
     except ValueError, err:
         self.assertEquals(str(err), 'Invalid path: o%0An%20e')
Пример #2
0
 def DELETE(self, req):
     """Handle HTTP DELETE request."""
     try:
         drive, part, account = split_path(unquote(req.path), 3)
     except ValueError, err:
         return HTTPBadRequest(body=str(err), content_type='text/plain',
                                                 request=req)
Пример #3
0
    def __call__(self, env, start_response):
        """
        Main hook into the WSGI paste.deploy filter/app pipeline.

        :param env: The WSGI environment dict.
        :param start_response: The WSGI start_response hook.
        """
        env['staticweb.start_time'] = time.time()
        try:
            (self.version, self.account, self.container, self.obj) = \
                split_path(env['PATH_INFO'], 2, 4, True)
        except ValueError:
            return self.app(env, start_response)
        memcache_client = cache_from_env(env)
        if memcache_client:
            if env['REQUEST_METHOD'] in ('PUT', 'POST'):
                if not self.obj and self.container:
                    memcache_key = '/staticweb/%s/%s/%s' % \
                        (self.version, self.account, self.container)
                    memcache_client.delete(memcache_key)
                return self.app(env, start_response)
        if (env['REQUEST_METHOD'] not in ('HEAD', 'GET') or
            (env.get('REMOTE_USER') and
             env.get('HTTP_X_WEB_MODE', 'f').lower() not in TRUE_VALUES) or
            (not env.get('REMOTE_USER') and
             env.get('HTTP_X_WEB_MODE', 't').lower() not in TRUE_VALUES)):
            return self.app(env, start_response)
        if self.obj:
            return self._handle_object(env, start_response)
        elif self.container:
            return self._handle_container(env, start_response)
        return self.app(env, start_response)
Пример #4
0
 def GET(self, req):
     """Handle HTTP GET request."""
     try:
         drive, part, account, container, obj = split_path(
             unquote(req.path), 4, 5, True)
     except ValueError, err:
         return HTTPBadRequest(body=str(err), content_type='text/plain',
                             request=req)
Пример #5
0
 def DELETE(self, request):
     """Handle HTTP DELETE requests for the Chase Object Server."""
     try:
         device, partition, account, container, obj = \
             split_path(unquote(request.path), 5, 5, True)
     except ValueError, e:
         return HTTPBadRequest(body=str(e), request=request,
                     content_type='text/plain')
Пример #6
0
 def REPLICATE(self, req):
     """
     Handle HTTP REPLICATE request (json-encoded RPC calls for replication.)
     """
     try:
         post_args = split_path(unquote(req.path), 3)
     except ValueError, err:
         return HTTPBadRequest(body=str(err), content_type='text/plain',
                             request=req)
Пример #7
0
    def get_controller(self, path):
        container, obj = split_path(path, 0, 2, True)
        d = dict(container_name=container, object_name=obj)

        if container and obj:
            return ObjectController, d
        elif container:
            return BucketController, d
        return ServiceController, d
Пример #8
0
 def HEAD(self, request):
     """Handle HTTP HEAD requests for the Chase Object Server."""
     try:
         device, partition, account, container, obj = \
             split_path(unquote(request.path), 5, 5, True)
     except ValueError, err:
         resp = HTTPBadRequest(request=request)
         resp.content_type = 'text/plain'
         resp.body = str(err)
         return resp
Пример #9
0
 def REPLICATE(self, request):
     """
     Handle REPLICATE requests for the Chase Object Server.  This is used
     by the object replicator to get hashes for directories.
     """
     try:
         device, partition, suffix = split_path(
             unquote(request.path), 2, 3, True)
     except ValueError, e:
         return HTTPBadRequest(body=str(e), request=request,
                               content_type='text/plain')
Пример #10
0
    def GET(self, req):
        error = False
        root, type = split_path(req.path, 1, 2, False)
        try:
            if type == "mem":
                content = json.dumps(self.get_mem())
            elif type == "load":
                try:
                    content = json.dumps(self.get_load(), sort_keys=True)
                except IOError as e:
                    error = True
                    content = "load - %s" % e
            elif type == "async":
                try:
                    content = json.dumps(self.get_async_info())
                except IOError as e:
                    error = True
                    content = "async - %s" % e
            elif type == "replication":
                try:
                    content = json.dumps(self.get_replication_info())
                except IOError as e:
                    error = True
                    content = "replication - %s" % e
            elif type == "mounted":
                content = json.dumps(self.get_mounted())
            elif type == "unmounted":
                content = json.dumps(self.get_unmounted())
            elif type == "diskusage":
                content = json.dumps(self.get_diskusage())
            elif type == "ringmd5":
                content = json.dumps(self.get_ring_md5())
            elif type == "quarantined":
                content = json.dumps(self.get_quarantine_count())
            elif type == "sockstat":
                content = json.dumps(self.get_socket_info())
            else:
                content = "Invalid path: %s" % req.path
                return Response(request=req, status="400 Bad Request", \
                    body=content, content_type="text/plain")
        except ValueError as e:
            error = True
            content = "ValueError: %s" % e

        if not error:
            return Response(request=req, body=content, \
                content_type="application/json")
        else:
            msg = 'CRITICAL recon - %s' % str(content)
            self.logger.critical(msg)
            body = "Internal server error."
            return Response(request=req, status="500 Server Error", \
                body=body, content_type="text/plain")
Пример #11
0
    def GET(self, req):
        error = False
        root, type = split_path(req.path, 1, 2, False)
        try:
            if type == "mem":
                content = json.dumps(self.get_mem())
            elif type == "load":
                try:
                    content = json.dumps(self.get_load(), sort_keys=True)
                except IOError as e:
                    error = True
                    content = "load - %s" % e
            elif type == "async":
                try:
                    content = json.dumps(self.get_async_info())
                except IOError as e:
                    error = True
                    content = "async - %s" % e
            elif type == "replication":
                try:
                    content = json.dumps(self.get_replication_info())
                except IOError as e:
                    error = True
                    content = "replication - %s" % e
            elif type == "mounted":
                content = json.dumps(self.get_mounted())
            elif type == "unmounted":
                content = json.dumps(self.get_unmounted())
            elif type == "diskusage":
                content = json.dumps(self.get_diskusage())
            elif type == "ringmd5":
                content = json.dumps(self.get_ring_md5())
            elif type == "quarantined":
                content = json.dumps(self.get_quarantine_count())
            elif type == "sockstat":
                content = json.dumps(self.get_socket_info())
            else:
                content = "Invalid path: %s" % req.path
                return Response(request=req, status="400 Bad Request", \
                    body=content, content_type="text/plain")
        except ValueError as e:
            error = True
            content = "ValueError: %s" % e

        if not error:
            return Response(request=req, body=content, \
                content_type="application/json")
        else:
            msg = 'CRITICAL recon - %s' % str(content)
            self.logger.critical(msg)
            body = "Internal server error."
            return Response(request=req, status="500 Server Error", \
                body=body, content_type="text/plain")
Пример #12
0
 def HEAD(self, req):
     """Handle HTTP HEAD request."""
     # TODO(refactor): The account server used to provide a 'account and
     # container existence check all-in-one' call by doing a HEAD with a
     # container path. However, container existence is now checked with the
     # container servers directly so this is no longer needed. We should
     # refactor out the container existence check here and retest
     # everything.
     try:
         drive, part, account, container = split_path(unquote(req.path),
                                                      3, 4)
     except ValueError, err:
         return HTTPBadRequest(body=str(err), content_type='text/plain',
                                                 request=req)
Пример #13
0
 def HEAD(self, req):
     """Handle HTTP HEAD request."""
     # TODO(refactor): The account server used to provide a 'account and
     # container existence check all-in-one' call by doing a HEAD with a
     # container path. However, container existence is now checked with the
     # container servers directly so this is no longer needed. We should
     # refactor out the container existence check here and retest
     # everything.
     try:
         drive, part, account, container = split_path(
             unquote(req.path), 3, 4)
     except ValueError, err:
         return HTTPBadRequest(body=str(err),
                               content_type='text/plain',
                               request=req)
Пример #14
0
 def authorize(self, req):
     """
     Returns None if the request is authorized to continue or a standard
     WSGI response callable if not.
     """
     try:
         version, account, container, obj = split_path(req.path, 1, 4, True)
     except ValueError:
         return HTTPNotFound(request=req)
     if not account or not account.startswith(self.reseller_prefix):
         return self.denied_response(req)
     user_groups = (req.remote_user or '').split(',')
     if '.reseller_admin' in user_groups and \
             account != self.reseller_prefix and \
             account[len(self.reseller_prefix)] != '.':
         req.environ['chase_owner'] = True
         return None
     if account in user_groups and \
             (req.method not in ('DELETE', 'PUT') or container):
         # If the user is admin for the account and is not trying to do an
         # account DELETE or PUT...
         req.environ['chase_owner'] = True
         return None
     if (req.environ.get('chase_sync_key') and
         req.environ['chase_sync_key'] ==
             req.headers.get('x-container-sync-key', None) and
         'x-timestamp' in req.headers and
         (req.remote_addr in self.allowed_sync_hosts or
          get_remote_client(req) in self.allowed_sync_hosts)):
         return None
     referrers, groups = parse_acl(getattr(req, 'acl', None))
     if referrer_allowed(req.referer, referrers):
         if obj or '.rlistings' in groups:
             return None
         return self.denied_response(req)
     if not req.remote_user:
         return self.denied_response(req)
     for user_group in user_groups:
         if user_group in groups:
             return None
     return self.denied_response(req)
Пример #15
0
 def authorize(self, req):
     """
     Returns None if the request is authorized to continue or a standard
     WSGI response callable if not.
     """
     try:
         version, account, container, obj = split_path(req.path, 1, 4, True)
     except ValueError:
         return HTTPNotFound(request=req)
     if not account or not account.startswith(self.reseller_prefix):
         return self.denied_response(req)
     user_groups = (req.remote_user or '').split(',')
     if '.reseller_admin' in user_groups and \
             account != self.reseller_prefix and \
             account[len(self.reseller_prefix)] != '.':
         req.environ['chase_owner'] = True
         return None
     if account in user_groups and \
             (req.method not in ('DELETE', 'PUT') or container):
         # If the user is admin for the account and is not trying to do an
         # account DELETE or PUT...
         req.environ['chase_owner'] = True
         return None
     if (req.environ.get('chase_sync_key')
             and req.environ['chase_sync_key'] == req.headers.get(
                 'x-container-sync-key', None)
             and 'x-timestamp' in req.headers
             and (req.remote_addr in self.allowed_sync_hosts
                  or get_remote_client(req) in self.allowed_sync_hosts)):
         return None
     referrers, groups = parse_acl(getattr(req, 'acl', None))
     if referrer_allowed(req.referer, referrers):
         if obj or '.rlistings' in groups:
             return None
         return self.denied_response(req)
     if not req.remote_user:
         return self.denied_response(req)
     for user_group in user_groups:
         if user_group in groups:
             return None
     return self.denied_response(req)
Пример #16
0
    def handle_request(self, req):
        """
        Entry point for auth requests (ones that match the self.auth_prefix).
        Should return a WSGI-style callable (such as webob.Response).

        :param req: webob.Request object
        """
        req.start_time = time()
        handler = None
        try:
            version, account, user, _junk = split_path(req.path_info,
                minsegs=1, maxsegs=4, rest_with_last=True)
        except ValueError:
            return HTTPNotFound(request=req)
        if version in ('v1', 'v1.0', 'auth'):
            if req.method == 'GET':
                handler = self.handle_get_token
        if not handler:
            req.response = HTTPBadRequest(request=req)
        else:
            req.response = handler(req)
        return req.response
Пример #17
0
    def authorize_via_acl(self, req):
        """Anon request handling.

        For now this only allows anon read of objects.  Container and account
        actions are prohibited.
        """

        self.log.debug("authorizing anonymous request")
        try:
            version, account, container, obj = split_path(req.path, 1, 4, True)
        except ValueError:
            return HTTPNotFound(request=req)

        if obj:
            return self._authorize_anon_object(req, account, container, obj)

        if container:
            return self._authorize_anon_container(req, account, container)

        if account:
            return self._authorize_anon_account(req, account)

        return self._authorize_anon_toplevel(req)
Пример #18
0
    def authorize_via_acl(self, req):
        """Anon request handling.

        For now this only allows anon read of objects.  Container and account
        actions are prohibited.
        """

        self.log.debug('authorizing anonymous request')
        try:
            version, account, container, obj = split_path(req.path, 1, 4, True)
        except ValueError:
            return HTTPNotFound(request=req)

        if obj:
            return self._authorize_anon_object(req, account, container, obj)

        if container:
            return self._authorize_anon_container(req, account, container)

        if account:
            return self._authorize_anon_account(req, account)

        return self._authorize_anon_toplevel(req)
Пример #19
0
    def __call__(self, env, start_response):
        """
        WSGI entry point.
        Wraps env in webob.Request object and passes it down.

        :param env: WSGI environment dictionary
        :param start_response: WSGI callable
        """
        req = Request(env)
        if self.memcache_client is None:
            self.memcache_client = cache_from_env(env)
        if not self.memcache_client:
            self.logger.warning(
                _('Warning: Cannot ratelimit without a memcached client'))
            return self.app(env, start_response)
        try:
            version, account, container, obj = split_path(req.path, 1, 4, True)
        except ValueError:
            return HTTPNotFound()(env, start_response)
        ratelimit_resp = self.handle_ratelimit(req, account, container, obj)
        if ratelimit_resp is None:
            return self.app(env, start_response)
        else:
            return ratelimit_resp(env, start_response)
Пример #20
0
    def handle_request(self, req):
        """
        Entry point for auth requests (ones that match the self.auth_prefix).
        Should return a WSGI-style callable (such as webob.Response).

        :param req: webob.Request object
        """
        req.start_time = time()
        handler = None
        try:
            version, account, user, _junk = split_path(req.path_info,
                                                       minsegs=1,
                                                       maxsegs=4,
                                                       rest_with_last=True)
        except ValueError:
            return HTTPNotFound(request=req)
        if version in ('v1', 'v1.0', 'auth'):
            if req.method == 'GET':
                handler = self.handle_get_token
        if not handler:
            req.response = HTTPBadRequest(request=req)
        else:
            req.response = handler(req)
        return req.response
Пример #21
0
    def __call__(self, env, start_response):
        """
        WSGI entry point.
        Wraps env in webob.Request object and passes it down.

        :param env: WSGI environment dictionary
        :param start_response: WSGI callable
        """
        req = Request(env)
        if self.memcache_client is None:
            self.memcache_client = cache_from_env(env)
        if not self.memcache_client:
            self.logger.warning(
                _('Warning: Cannot ratelimit without a memcached client'))
            return self.app(env, start_response)
        try:
            version, account, container, obj = split_path(req.path, 1, 4, True)
        except ValueError:
            return HTTPNotFound()(env, start_response)
        ratelimit_resp = self.handle_ratelimit(req, account, container, obj)
        if ratelimit_resp is None:
            return self.app(env, start_response)
        else:
            return ratelimit_resp(env, start_response)
Пример #22
0
    def handle_get_token(self, req):
        """
        Handles the various `request for token and service end point(s)` calls.
        There are various formats to support the various auth servers in the
        past. Examples::

            GET <auth-prefix>/v1/<act>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/v1.0
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>

        On successful authentication, the response will have X-Auth-Token and
        X-Storage-Token set to the token to use with Chase and X-Storage-URL
        set to the URL to the default Chase cluster to use.

        :param req: The webob.Request to process.
        :returns: webob.Response, 2xx on success with data set as explained
                  above.
        """
        # Validate the request info
        try:
            pathsegs = split_path(req.path_info,
                                  minsegs=1,
                                  maxsegs=3,
                                  rest_with_last=True)
        except ValueError:
            return HTTPNotFound(request=req)
        if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
            account = pathsegs[1]
            user = req.headers.get('x-storage-user')
            if not user:
                user = req.headers.get('x-auth-user')
                if not user or ':' not in user:
                    return HTTPUnauthorized(request=req)
                account2, user = user.split(':', 1)
                if account != account2:
                    return HTTPUnauthorized(request=req)
            key = req.headers.get('x-storage-pass')
            if not key:
                key = req.headers.get('x-auth-key')
        elif pathsegs[0] in ('auth', 'v1.0'):
            user = req.headers.get('x-auth-user')
            if not user:
                user = req.headers.get('x-storage-user')
            if not user or ':' not in user:
                return HTTPUnauthorized(request=req)
            account, user = user.split(':', 1)
            key = req.headers.get('x-auth-key')
            if not key:
                key = req.headers.get('x-storage-pass')
        else:
            return HTTPBadRequest(request=req)
        if not all((account, user, key)):
            return HTTPUnauthorized(request=req)
        # Authenticate user
        account_user = account + ':' + user
        if account_user not in self.users:
            return HTTPUnauthorized(request=req)
        if self.users[account_user]['key'] != key:
            return HTTPUnauthorized(request=req)
        # Get memcache client
        memcache_client = cache_from_env(req.environ)
        if not memcache_client:
            raise Exception('Memcache required')
        # See if a token already exists and hasn't expired
        token = None
        memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user)
        candidate_token = memcache_client.get(memcache_user_key)
        if candidate_token:
            memcache_token_key = \
                '%s/token/%s' % (self.reseller_prefix, candidate_token)
            cached_auth_data = memcache_client.get(memcache_token_key)
            if cached_auth_data:
                expires, groups = cached_auth_data
                if expires > time():
                    token = candidate_token
        # Create a new token if one didn't exist
        if not token:
            # Generate new token
            token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
            expires = time() + self.token_life
            groups = [account, account_user]
            groups.extend(self.users[account_user]['groups'])
            if '.admin' in groups:
                groups.remove('.admin')
                account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
                groups.append(account_id)
            groups = ','.join(groups)
            # Save token
            memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
            memcache_client.set(memcache_token_key, (expires, groups),
                                timeout=float(expires - time()))
            # Record the token with the user info for future use.
            memcache_user_key = \
                '%s/user/%s' % (self.reseller_prefix, account_user)
            memcache_client.set(memcache_user_key,
                                token,
                                timeout=float(expires - time()))
        return Response(request=req,
                        headers={
                            'x-auth-token': token,
                            'x-storage-token': token,
                            'x-storage-url': self.users[account_user]['url']
                        })
Пример #23
0
    def __call__(self, env, start_response):
        """
        Accepts a standard WSGI application call, authenticating the request
        and installing callback hooks for authorization and ACL header
        validation. For an authenticated request, REMOTE_USER will be set to a
        comma separated list of the user's groups.

        With a non-empty reseller prefix, acts as the definitive auth service
        for just tokens and accounts that begin with that prefix, but will deny
        requests outside this prefix if no other auth middleware overrides it.

        With an empty reseller prefix, acts as the definitive auth service only
        for tokens that validate to a non-empty set of groups. For all other
        requests, acts as the fallback auth service when no other auth
        middleware overrides it.

        Alternatively, if the request matches the self.auth_prefix, the request
        will be routed through the internal auth request handler (self.handle).
        This is to handle granting tokens, etc.
        """
        if env.get('PATH_INFO', '').startswith(self.auth_prefix):
            return self.handle(env, start_response)
        s3 = env.get('HTTP_AUTHORIZATION')
        token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
        if s3 or (token and token.startswith(self.reseller_prefix)):
            # Note: Empty reseller_prefix will match all tokens.
            groups = self.get_groups(env, token)
            if groups:
                env['REMOTE_USER'] = groups
                user = groups and groups.split(',', 1)[0] or ''
                # We know the proxy logs the token, so we augment it just a bit
                # to also log the authenticated user.
                env['HTTP_X_AUTH_TOKEN'] = \
                    '%s,%s' % (user, 's3' if s3 else token)
                env['chase.authorize'] = self.authorize
                env['chase.clean_acl'] = clean_acl
            else:
                # Unauthorized token
                if self.reseller_prefix:
                    # Because I know I'm the definitive auth for this token, I
                    # can deny it outright.
                    return HTTPUnauthorized()(env, start_response)
                # Because I'm not certain if I'm the definitive auth for empty
                # reseller_prefixed tokens, I won't overwrite chase.authorize.
                elif 'chase.authorize' not in env:
                    env['chase.authorize'] = self.denied_response
        else:
            if self.reseller_prefix:
                # With a non-empty reseller_prefix, I would like to be called
                # back for anonymous access to accounts I know I'm the
                # definitive auth for.
                try:
                    version, rest = split_path(env.get('PATH_INFO', ''), 1, 2,
                                               True)
                except ValueError:
                    return HTTPNotFound()(env, start_response)
                if rest and rest.startswith(self.reseller_prefix):
                    # Handle anonymous access to accounts I'm the definitive
                    # auth for.
                    env['chase.authorize'] = self.authorize
                    env['chase.clean_acl'] = clean_acl
                # Not my token, not my account, I can't authorize this request,
                # deny all is a good idea if not already set...
                elif 'chase.authorize' not in env:
                    env['chase.authorize'] = self.denied_response
            # Because I'm not certain if I'm the definitive auth for empty
            # reseller_prefixed accounts, I won't overwrite chase.authorize.
            elif 'chase.authorize' not in env:
                env['chase.authorize'] = self.authorize
                env['chase.clean_acl'] = clean_acl
        return self.app(env, start_response)
Пример #24
0
class TestUtils(unittest.TestCase):
    """ Tests for chase.common.utils """
    def setUp(self):
        utils.HASH_PATH_SUFFIX = 'endcap'

    def test_normalize_timestamp(self):
        """ Test chase.common.utils.normalize_timestamp """
        self.assertEquals(utils.normalize_timestamp('1253327593.48174'),
                          "1253327593.48174")
        self.assertEquals(utils.normalize_timestamp(1253327593.48174),
                          "1253327593.48174")
        self.assertEquals(utils.normalize_timestamp('1253327593.48'),
                          "1253327593.48000")
        self.assertEquals(utils.normalize_timestamp(1253327593.48),
                          "1253327593.48000")
        self.assertEquals(utils.normalize_timestamp('253327593.48'),
                          "0253327593.48000")
        self.assertEquals(utils.normalize_timestamp(253327593.48),
                          "0253327593.48000")
        self.assertEquals(utils.normalize_timestamp('1253327593'),
                          "1253327593.00000")
        self.assertEquals(utils.normalize_timestamp(1253327593),
                          "1253327593.00000")
        self.assertRaises(ValueError, utils.normalize_timestamp, '')
        self.assertRaises(ValueError, utils.normalize_timestamp, 'abc')

    def test_mkdirs(self):
        testroot = os.path.join(os.path.dirname(__file__), 'mkdirs')
        try:
            os.unlink(testroot)
        except Exception:
            pass
        rmtree(testroot, ignore_errors=1)
        self.assert_(not os.path.exists(testroot))
        utils.mkdirs(testroot)
        self.assert_(os.path.exists(testroot))
        utils.mkdirs(testroot)
        self.assert_(os.path.exists(testroot))
        rmtree(testroot, ignore_errors=1)

        testdir = os.path.join(testroot, 'one/two/three')
        self.assert_(not os.path.exists(testdir))
        utils.mkdirs(testdir)
        self.assert_(os.path.exists(testdir))
        utils.mkdirs(testdir)
        self.assert_(os.path.exists(testdir))
        rmtree(testroot, ignore_errors=1)

        open(testroot, 'wb').close()
        self.assert_(not os.path.exists(testdir))
        self.assertRaises(OSError, utils.mkdirs, testdir)
        os.unlink(testroot)

    def test_split_path(self):
        """ Test chase.common.utils.split_account_path """
        self.assertRaises(ValueError, utils.split_path, '')
        self.assertRaises(ValueError, utils.split_path, '/')
        self.assertRaises(ValueError, utils.split_path, '//')
        self.assertEquals(utils.split_path('/a'), ['a'])
        self.assertRaises(ValueError, utils.split_path, '//a')
        self.assertEquals(utils.split_path('/a/'), ['a'])
        self.assertRaises(ValueError, utils.split_path, '/a/c')
        self.assertRaises(ValueError, utils.split_path, '//c')
        self.assertRaises(ValueError, utils.split_path, '/a/c/')
        self.assertRaises(ValueError, utils.split_path, '/a//')
        self.assertRaises(ValueError, utils.split_path, '/a', 2)
        self.assertRaises(ValueError, utils.split_path, '/a', 2, 3)
        self.assertRaises(ValueError, utils.split_path, '/a', 2, 3, True)
        self.assertEquals(utils.split_path('/a/c', 2), ['a', 'c'])
        self.assertEquals(utils.split_path('/a/c/o', 3), ['a', 'c', 'o'])
        self.assertRaises(ValueError, utils.split_path, '/a/c/o/r', 3, 3)
        self.assertEquals(utils.split_path('/a/c/o/r', 3, 3, True),
                          ['a', 'c', 'o/r'])
        self.assertEquals(utils.split_path('/a/c', 2, 3, True),
                          ['a', 'c', None])
        self.assertRaises(ValueError, utils.split_path, '/a', 5, 4)
        self.assertEquals(utils.split_path('/a/c/', 2), ['a', 'c'])
        self.assertEquals(utils.split_path('/a/c/', 2, 3), ['a', 'c', ''])
        try:
            utils.split_path('o\nn e', 2)
        except ValueError, err:
            self.assertEquals(str(err), 'Invalid path: o%0An%20e')
        try:
            utils.split_path('o\nn e', 2, 3, True)
        except ValueError, err:
            self.assertEquals(str(err), 'Invalid path: o%0An%20e')
Пример #25
0
    def __call__(self, env, start_response):
        """
        Accepts a standard WSGI application call, authenticating the request
        and installing callback hooks for authorization and ACL header
        validation. For an authenticated request, REMOTE_USER will be set to a
        comma separated list of the user's groups.

        With a non-empty reseller prefix, acts as the definitive auth service
        for just tokens and accounts that begin with that prefix, but will deny
        requests outside this prefix if no other auth middleware overrides it.

        With an empty reseller prefix, acts as the definitive auth service only
        for tokens that validate to a non-empty set of groups. For all other
        requests, acts as the fallback auth service when no other auth
        middleware overrides it.

        Alternatively, if the request matches the self.auth_prefix, the request
        will be routed through the internal auth request handler (self.handle).
        This is to handle granting tokens, etc.
        """
        if env.get('PATH_INFO', '').startswith(self.auth_prefix):
            return self.handle(env, start_response)
        s3 = env.get('HTTP_AUTHORIZATION')
        token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
        if s3 or (token and token.startswith(self.reseller_prefix)):
            # Note: Empty reseller_prefix will match all tokens.
            groups = self.get_groups(env, token)
            if groups:
                env['REMOTE_USER'] = groups
                user = groups and groups.split(',', 1)[0] or ''
                # We know the proxy logs the token, so we augment it just a bit
                # to also log the authenticated user.
                env['HTTP_X_AUTH_TOKEN'] = \
                    '%s,%s' % (user, 's3' if s3 else token)
                env['chase.authorize'] = self.authorize
                env['chase.clean_acl'] = clean_acl
            else:
                # Unauthorized token
                if self.reseller_prefix:
                    # Because I know I'm the definitive auth for this token, I
                    # can deny it outright.
                    return HTTPUnauthorized()(env, start_response)
                # Because I'm not certain if I'm the definitive auth for empty
                # reseller_prefixed tokens, I won't overwrite chase.authorize.
                elif 'chase.authorize' not in env:
                    env['chase.authorize'] = self.denied_response
        else:
            if self.reseller_prefix:
                # With a non-empty reseller_prefix, I would like to be called
                # back for anonymous access to accounts I know I'm the
                # definitive auth for.
                try:
                    version, rest = split_path(env.get('PATH_INFO', ''),
                                               1, 2, True)
                except ValueError:
                    return HTTPNotFound()(env, start_response)
                if rest and rest.startswith(self.reseller_prefix):
                    # Handle anonymous access to accounts I'm the definitive
                    # auth for.
                    env['chase.authorize'] = self.authorize
                    env['chase.clean_acl'] = clean_acl
                # Not my token, not my account, I can't authorize this request,
                # deny all is a good idea if not already set...
                elif 'chase.authorize' not in env:
                    env['chase.authorize'] = self.denied_response
            # Because I'm not certain if I'm the definitive auth for empty
            # reseller_prefixed accounts, I won't overwrite chase.authorize.
            elif 'chase.authorize' not in env:
                env['chase.authorize'] = self.authorize
                env['chase.clean_acl'] = clean_acl
        return self.app(env, start_response)
Пример #26
0
    def handle_get_token(self, req):
        """
        Handles the various `request for token and service end point(s)` calls.
        There are various formats to support the various auth servers in the
        past. Examples::

            GET <auth-prefix>/v1/<act>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/v1.0
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>

        On successful authentication, the response will have X-Auth-Token and
        X-Storage-Token set to the token to use with Chase and X-Storage-URL
        set to the URL to the default Chase cluster to use.

        :param req: The webob.Request to process.
        :returns: webob.Response, 2xx on success with data set as explained
                  above.
        """
        # Validate the request info
        try:
            pathsegs = split_path(req.path_info, minsegs=1, maxsegs=3,
                                  rest_with_last=True)
        except ValueError:
            return HTTPNotFound(request=req)
        if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
            account = pathsegs[1]
            user = req.headers.get('x-storage-user')
            if not user:
                user = req.headers.get('x-auth-user')
                if not user or ':' not in user:
                    return HTTPUnauthorized(request=req)
                account2, user = user.split(':', 1)
                if account != account2:
                    return HTTPUnauthorized(request=req)
            key = req.headers.get('x-storage-pass')
            if not key:
                key = req.headers.get('x-auth-key')
        elif pathsegs[0] in ('auth', 'v1.0'):
            user = req.headers.get('x-auth-user')
            if not user:
                user = req.headers.get('x-storage-user')
            if not user or ':' not in user:
                return HTTPUnauthorized(request=req)
            account, user = user.split(':', 1)
            key = req.headers.get('x-auth-key')
            if not key:
                key = req.headers.get('x-storage-pass')
        else:
            return HTTPBadRequest(request=req)
        if not all((account, user, key)):
            return HTTPUnauthorized(request=req)
        # Authenticate user
        account_user = account + ':' + user
        if account_user not in self.users:
            return HTTPUnauthorized(request=req)
        if self.users[account_user]['key'] != key:
            return HTTPUnauthorized(request=req)
        # Get memcache client
        memcache_client = cache_from_env(req.environ)
        if not memcache_client:
            raise Exception('Memcache required')
        # See if a token already exists and hasn't expired
        token = None
        memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user)
        candidate_token = memcache_client.get(memcache_user_key)
        if candidate_token:
            memcache_token_key = \
                '%s/token/%s' % (self.reseller_prefix, candidate_token)
            cached_auth_data = memcache_client.get(memcache_token_key)
            if cached_auth_data:
                expires, groups = cached_auth_data
                if expires > time():
                    token = candidate_token
        # Create a new token if one didn't exist
        if not token:
            # Generate new token
            token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
            expires = time() + self.token_life
            groups = [account, account_user]
            groups.extend(self.users[account_user]['groups'])
            if '.admin' in groups:
                groups.remove('.admin')
                account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
                groups.append(account_id)
            groups = ','.join(groups)
            # Save token
            memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
            memcache_client.set(memcache_token_key, (expires, groups),
                                timeout=float(expires - time()))
            # Record the token with the user info for future use.
            memcache_user_key = \
                '%s/user/%s' % (self.reseller_prefix, account_user)
            memcache_client.set(memcache_user_key, token,
                                timeout=float(expires - time()))
        return Response(request=req,
            headers={'x-auth-token': token, 'x-storage-token': token,
                     'x-storage-url': self.users[account_user]['url']})