Example #1
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)
Example #2
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)
Example #3
0
 def __call__(self, env, start_response):
     if not self.storage_domain:
         return self.app(env, start_response)
     given_domain = env['HTTP_HOST']
     port = ''
     if ':' in given_domain:
         given_domain, port = given_domain.rsplit(':', 1)
     if given_domain == self.storage_domain[1:]:  # strip initial '.'
         return self.app(env, start_response)
     a_domain = given_domain
     if not a_domain.endswith(self.storage_domain):
         if self.memcache is None:
             self.memcache = cache_from_env(env)
         error = True
         for tries in xrange(self.lookup_depth):
             found_domain = None
             if self.memcache:
                 memcache_key = ''.join(['cname-', a_domain])
                 found_domain = self.memcache.get(memcache_key)
             if not found_domain:
                 ttl, found_domain = lookup_cname(a_domain)
                 if self.memcache:
                     memcache_key = ''.join(['cname-', given_domain])
                     self.memcache.set(memcache_key, found_domain,
                                       timeout=ttl)
             if found_domain is None or found_domain == a_domain:
                 # no CNAME records or we're at the last lookup
                 error = True
                 found_domain = None
                 break
             elif found_domain.endswith(self.storage_domain):
                 # Found it!
                 self.logger.info(
                     _('Mapped %(given_domain)s to %(found_domain)s') %
                     {'given_domain': given_domain,
                      'found_domain': found_domain})
                 if port:
                     env['HTTP_HOST'] = ':'.join([found_domain, port])
                 else:
                     env['HTTP_HOST'] = found_domain
                 error = False
                 break
             else:
                 # try one more deep in the chain
                 self.logger.debug(_('Following CNAME chain for  ' \
                         '%(given_domain)s to %(found_domain)s') %
                         {'given_domain': given_domain,
                          'found_domain': found_domain})
                 a_domain = found_domain
         if error:
             if found_domain:
                 msg = 'CNAME lookup failed after %d tries' % \
                         self.lookup_depth
             else:
                 msg = 'CNAME lookup failed to resolve to a valid domain'
             resp = HTTPBadRequest(request=Request(env), body=msg,
                                   content_type='text/plain')
             return resp(env, start_response)
     return self.app(env, start_response)
Example #4
0
    def get_groups(self, env, token):
        """
        Get groups for the given token.

        :param env: The current WSGI environment dictionary.
        :param token: Token to validate and return a group string for.

        :returns: None if the token is invalid or a string containing a comma
                  separated list of groups the authenticated user is a member
                  of. The first group in the list is also considered a unique
                  identifier for that user.
        """
        groups = None
        memcache_client = cache_from_env(env)
        if not memcache_client:
            raise Exception('Memcache required')
        memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
        cached_auth_data = memcache_client.get(memcache_token_key)
        if cached_auth_data:
            expires, groups = cached_auth_data
            if expires < time():
                groups = None

        if env.get('HTTP_AUTHORIZATION'):
            account_user, sign = \
                env['HTTP_AUTHORIZATION'].split(' ')[1].rsplit(':', 1)
            if account_user not in self.users:
                return None
            account, user = account_user.split(':', 1)
            account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
            path = env['PATH_INFO']
            env['PATH_INFO'] = path.replace(account_user, account_id, 1)
            msg = base64.urlsafe_b64decode(unquote(token))
            key = self.users[account_user]['key']
            s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
            if s != sign:
                return None
            groups = [account, account_user]
            groups.extend(self.users[account_user]['groups'])
            if '.admin' in groups:
                groups.remove('.admin')
                groups.append(account_id)
            groups = ','.join(groups)

        return groups
Example #5
0
    def get_groups(self, env, token):
        """
        Get groups for the given token.

        :param env: The current WSGI environment dictionary.
        :param token: Token to validate and return a group string for.

        :returns: None if the token is invalid or a string containing a comma
                  separated list of groups the authenticated user is a member
                  of. The first group in the list is also considered a unique
                  identifier for that user.
        """
        groups = None
        memcache_client = cache_from_env(env)
        if not memcache_client:
            raise Exception('Memcache required')
        memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
        cached_auth_data = memcache_client.get(memcache_token_key)
        if cached_auth_data:
            expires, groups = cached_auth_data
            if expires < time():
                groups = None

        if env.get('HTTP_AUTHORIZATION'):
            account_user, sign = \
                env['HTTP_AUTHORIZATION'].split(' ')[1].rsplit(':', 1)
            if account_user not in self.users:
                return None
            account, user = account_user.split(':', 1)
            account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
            path = env['PATH_INFO']
            env['PATH_INFO'] = path.replace(account_user, account_id, 1)
            msg = base64.urlsafe_b64decode(unquote(token))
            key = self.users[account_user]['key']
            s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
            if s != sign:
                return None
            groups = [account, account_user]
            groups.extend(self.users[account_user]['groups'])
            if '.admin' in groups:
                groups.remove('.admin')
                groups.append(account_id)
            groups = ','.join(groups)

        return groups
Example #6
0
    def _get_container_info(self, env, start_response):
        """
        Retrieves x-container-meta-web-index, x-container-meta-web-error,
        x-container-meta-web-listings, and x-container-meta-web-listings-css
        from memcache or from the cluster and stores the result in memcache and
        in self._index, self._error, self._listings, and self._listings_css.

        :param env: The WSGI environment dict.
        :param start_response: The WSGI start_response hook.
        """
        self._index = self._error = self._listings = self._listings_css = None
        memcache_client = cache_from_env(env)
        if memcache_client:
            memcache_key = '/staticweb/%s/%s/%s' % (self.version, self.account,
                                                    self.container)
            cached_data = memcache_client.get(memcache_key)
            if cached_data:
                (self._index, self._error, self._listings,
                 self._listings_css) = cached_data
                return
        tmp_env = self._get_escalated_env(env)
        tmp_env['REQUEST_METHOD'] = 'HEAD'
        req = Request.blank('/%s/%s/%s' %
                            (self.version, self.account, self.container),
                            environ=tmp_env)
        resp = req.get_response(self.app)
        if resp.status_int // 100 == 2:
            self._index = \
                resp.headers.get('x-container-meta-web-index', '').strip()
            self._error = \
                resp.headers.get('x-container-meta-web-error', '').strip()
            self._listings = \
                resp.headers.get('x-container-meta-web-listings', '').strip()
            self._listings_css = \
                resp.headers.get('x-container-meta-web-listings-css',
                                 '').strip()
            if memcache_client:
                memcache_client.set(memcache_key,
                                    (self._index, self._error, self._listings,
                                     self._listings_css),
                                    timeout=self.cache_timeout)
Example #7
0
    def _get_container_info(self, env, start_response):
        """
        Retrieves x-container-meta-web-index, x-container-meta-web-error,
        x-container-meta-web-listings, and x-container-meta-web-listings-css
        from memcache or from the cluster and stores the result in memcache and
        in self._index, self._error, self._listings, and self._listings_css.

        :param env: The WSGI environment dict.
        :param start_response: The WSGI start_response hook.
        """
        self._index = self._error = self._listings = self._listings_css = None
        memcache_client = cache_from_env(env)
        if memcache_client:
            memcache_key = '/staticweb/%s/%s/%s' % (self.version, self.account,
                                                    self.container)
            cached_data = memcache_client.get(memcache_key)
            if cached_data:
                (self._index, self._error, self._listings,
                 self._listings_css) = cached_data
                return
        tmp_env = self._get_escalated_env(env)
        tmp_env['REQUEST_METHOD'] = 'HEAD'
        req = Request.blank('/%s/%s/%s' % (self.version, self.account,
            self.container), environ=tmp_env)
        resp = req.get_response(self.app)
        if resp.status_int // 100 == 2:
            self._index = \
                resp.headers.get('x-container-meta-web-index', '').strip()
            self._error = \
                resp.headers.get('x-container-meta-web-error', '').strip()
            self._listings = \
                resp.headers.get('x-container-meta-web-listings', '').strip()
            self._listings_css = \
                resp.headers.get('x-container-meta-web-listings-css',
                                 '').strip()
            if memcache_client:
                memcache_client.set(memcache_key,
                    (self._index, self._error, self._listings,
                     self._listings_css),
                    timeout=self.cache_timeout)
Example #8
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)
Example #9
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)
Example #10
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']
                        })
Example #11
0
 def __call__(self, env, start_response):
     if not self.storage_domain:
         return self.app(env, start_response)
     given_domain = env['HTTP_HOST']
     port = ''
     if ':' in given_domain:
         given_domain, port = given_domain.rsplit(':', 1)
     if given_domain == self.storage_domain[1:]:  # strip initial '.'
         return self.app(env, start_response)
     a_domain = given_domain
     if not a_domain.endswith(self.storage_domain):
         if self.memcache is None:
             self.memcache = cache_from_env(env)
         error = True
         for tries in xrange(self.lookup_depth):
             found_domain = None
             if self.memcache:
                 memcache_key = ''.join(['cname-', a_domain])
                 found_domain = self.memcache.get(memcache_key)
             if not found_domain:
                 ttl, found_domain = lookup_cname(a_domain)
                 if self.memcache:
                     memcache_key = ''.join(['cname-', given_domain])
                     self.memcache.set(memcache_key,
                                       found_domain,
                                       timeout=ttl)
             if found_domain is None or found_domain == a_domain:
                 # no CNAME records or we're at the last lookup
                 error = True
                 found_domain = None
                 break
             elif found_domain.endswith(self.storage_domain):
                 # Found it!
                 self.logger.info(
                     _('Mapped %(given_domain)s to %(found_domain)s') % {
                         'given_domain': given_domain,
                         'found_domain': found_domain
                     })
                 if port:
                     env['HTTP_HOST'] = ':'.join([found_domain, port])
                 else:
                     env['HTTP_HOST'] = found_domain
                 error = False
                 break
             else:
                 # try one more deep in the chain
                 self.logger.debug(_('Following CNAME chain for  ' \
                         '%(given_domain)s to %(found_domain)s') %
                         {'given_domain': given_domain,
                          'found_domain': found_domain})
                 a_domain = found_domain
         if error:
             if found_domain:
                 msg = 'CNAME lookup failed after %d tries' % \
                         self.lookup_depth
             else:
                 msg = 'CNAME lookup failed to resolve to a valid domain'
             resp = HTTPBadRequest(request=Request(env),
                                   body=msg,
                                   content_type='text/plain')
             return resp(env, start_response)
     return self.app(env, start_response)
Example #12
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']})