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)
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)
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
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)
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)
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'] })
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)
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']})