def test_cookie_set(): """ test that we get a cookie relating to the space we are in """ store = get_store(config) hostname = "foo.0.0.0.0:8080" user = User(u"f\u00F6o") user.set_password("foobar") store.put(user) user_cookie = get_auth(u"f\u00F6o", "foobar") response, content = http.request( "http://foo.0.0.0.0:8080/", method="GET", headers={"Cookie": 'tiddlyweb_user="******"' % user_cookie} ) assert response["status"] == "200", content time = datetime.utcnow().strftime("%Y%m%d%H") cookie = "csrf_token=%s:%s:%s" % ( time, user.usersign, sha("%s:%s:%s:%s" % (user.usersign, time, hostname, config["secret"])).hexdigest(), ) assert response["set-cookie"] == quote(cookie.encode("utf-8"), safe=".!~*'():=")
def _expand_recipe(content, url=''): """ Expand a recipe into a list of usable URLs. Content is a string, potentially with URL quoted strings. """ urls = [] for line in content.splitlines(): line = line.lstrip().rstrip() try: target_type, target = line.split(':', 1) except ValueError: continue # blank line in recipe if target_type in ACCEPTED_RECIPE_TYPES: target = target.lstrip().rstrip() # translate well-known variables for name in COOK_VARIABLES: target = target.replace('$%s' % name, COOK_VARIABLES[name]) # Check to see if the target is a URL (has a scheme) # if not we want to join it to the current url before # carrying on. scheme, _ = splittype(target) if not scheme: if not '%' in target: target = quote(target) target = urljoin(url, target) if target_type == 'recipe': urls.extend(recipe_to_urls(target)) else: urls.append(target) return urls
def recipe_as(self, recipe): """ :py:class:`Recipe <tiddlyweb.model.recipe.Recipe>` as HTML, including a link to the tiddlers within. """ self.environ['tiddlyweb.title'] = 'Recipe %s' % recipe.name lines = [] for bag, filter_string in recipe.get_recipe(): line = '<li><a href="' line += '%s/bags/%s/tiddlers' % (self._server_prefix(), encode_name(bag)) if filter_string: line += '?%s' % quote(filter_string.encode('utf-8'), safe=':=;') line += '">bag: %s filter:%s</a></li>' % (bag, filter_string) lines.append(line) output = "\n".join(lines) tiddler_link = '%s/tiddlers' % encode_name(recipe.name) return """ %s <div class="tiddlerslink"><a href="%s">Tiddlers in Recipe</a></div> <div id="recipedesc" class="description">%s</div> <ul id="recipe" class="listing"> %s </ul> %s """ % (self._header(), tiddler_link, recipe.desc, output, self._footer())
def recipe_as(self, recipe): """ Dump a :py:class:`recipe <tiddlyweb.model.recipe.Recipe>` as text. """ policy_dict = dict([(key, getattr(recipe.policy, key)) for key in Policy.attributes]) lines = [ 'desc: %s' % recipe.desc, 'policy: %s' % simplejson.dumps(policy_dict), '' ] for bag, filter_string in recipe.get_recipe(): line = '' if not get_bag_retriever(self.environ, bag): # If there is a retriever for this bag name then # we want to write its name straight. line += '/bags/%s/tiddlers' % quote(bag.encode('utf-8'), safe='') else: line += bag if filter_string: line += '?%s' % filter_string lines.append(line) return "\n".join(lines)
def recipe_as(self, recipe): """ :py:class:`Recipe <tiddlyweb.model.recipe.Recipe>` as HTML, including a link to the tiddlers within. """ self.environ['tiddlyweb.title'] = 'Recipe %s' % recipe.name lines = [] for bag, filter_string in recipe.get_recipe(): line = '<li><a href="' line += '%s/bags/%s/tiddlers' % ( self._server_prefix(), encode_name(bag)) if filter_string: line += '?%s' % quote( filter_string.encode('utf-8'), safe=':=;') line += '">bag: %s filter:%s</a></li>' % (bag, filter_string) lines.append(line) output = "\n".join(lines) tiddler_link = '%s/tiddlers' % encode_name(recipe.name) return """ %s <div class="tiddlerslink"><a href="%s">Tiddlers in Recipe</a></div> <div id="recipedesc" class="description">%s</div> <ul id="recipe" class="listing"> %s </ul> %s """ % (self._header(), tiddler_link, recipe.desc, output, self._footer())
def _challenger_url(environ, system): """ Return the proper URL for a specific challenger system. """ default_redirect = "%s/" % environ["tiddlyweb.config"]["server_prefix"] redirect = environ["tiddlyweb.query"].get("tiddlyweb_redirect", [default_redirect])[0] redirect = "?tiddlyweb_redirect=%s" % quote(redirect.encode("utf-8"), safe="") return "%s/challenge/%s%s" % (server_base_url(environ), system, redirect)
def _challenger_url(environ, system): """ Return the proper URL for a specific challenger system. """ default_redirect = '%s/' % environ['tiddlyweb.config']['server_prefix'] redirect = (environ['tiddlyweb.query'].get('tiddlyweb_redirect', [default_redirect])[0]) redirect = '?tiddlyweb_redirect=%s' % quote( redirect.encode('utf-8'), safe='') return '%s/challenge/%s%s' % (server_base_url(environ), system, redirect)
def _challenger_url(environ, system): """ Return the proper URL for a specific challenger system. """ default_redirect = '%s/' % environ['tiddlyweb.config']['server_prefix'] redirect = (environ['tiddlyweb.query'].get('tiddlyweb_redirect', [default_redirect])[0]) redirect = '?tiddlyweb_redirect=%s' % quote(redirect.encode('utf-8'), safe='') return '%s/challenge/%s%s' % (server_base_url(environ), system, redirect)
def post_tiddler_to_container(environ, start_response): """ entry point for recipes/foo/tiddlers or bags/foo/tiddlers we have included the tiddler name in the form, so get that and carry on as normal """ form = environ['tiddlyweb.query'] def get_name(): if 'title' in form: return retrieve_item(form, 'title') else: files = environ['tiddlyweb.input_files'] return files[0].filename try: tiddler_name = get_name() except (KeyError, IndexError): tiddler_name = str(uuid4()) Serialization.form = form try: redirect = environ['tiddlyweb.query'].pop('redirect') except KeyError: redirect = None if redirect: redirect = redirect[0] if '?' in redirect and not redirect.endswith('?'): redirect += '&' else: redirect += '?' redirect += '.no-cache=%s' % uuid4() # Extra safe characters used to preserve query strings. redirect = quote(redirect.encode('utf-8'), safe="/?&=:.!~*'()") #mock up some objects that tiddlyweb.web.handler.tiddler.put requires environ['wsgiorg.routing_args'][1]['tiddler_name'] = tiddler_name environ['REQUEST_METHOD'] = 'PUT' environ['wsgi.input'] = StringIO('dummy input') def dummy_start_response(response_code, *args): """ start_response may only be called once. We may need it to be a redirect instead of a 204 """ if not response_code.startswith('204'): start_response(response_code, *args) elif redirect: response = [('Location', redirect)] start_response('303 See Other', response) else: start_response(response_code, *args) return put(environ, dummy_start_response)
def _encode_filename(filename): """ utf-8 encode, then url escape, some filename, making it easy to use on various filesystems. Also check for no ../ in filenames. """ if not filename or '../' in filename: raise StoreEncodingError('invalid name for entity') return quote(filename.encode('utf-8'), safe=".!~*'()")
def _challenge_url(environ): """ Generate the URL of the challenge system so that GET requests are redirected to the right place. """ script_name = environ.get('SCRIPT_NAME', '') query_string = environ.get('QUERY_STRING', None) redirect = script_name if query_string: redirect += '?%s' % query_string redirect = quote(redirect, safe='') return '%s/challenge?tiddlyweb_redirect=%s' % ( server_base_url(environ), redirect)
def test_post_data_form_urlencoded(): """ test that a form POST requires a nonce test using application/x-www-form-urlencoded """ store = get_store(config) hostname = "foo.0.0.0.0:8080" user = User(u"f\u00F6o") user.set_password("foobar") store.put(user) store.put(Bag("foo_public")) timestamp = datetime.utcnow().strftime("%Y%m%d%H") secret = config["secret"] nonce = "%s:%s:%s" % ( timestamp, user.usersign, sha("%s:%s:%s:%s" % (user.usersign, timestamp, hostname, secret)).hexdigest(), ) user_cookie = get_auth(u"f\u00F6o", "foobar") csrf_token = 'csrf_token="%s"' % quote(nonce.encode("utf-8"), safe=".!~*'():") data = "title=foobar&text=hello%20world" print "nc", nonce, csrf_token # test success response, content = http.request( "http://foo.0.0.0.0:8080/bags/foo_public/tiddlers", method="POST", headers={ "Content-type": "application/x-www-form-urlencoded", "Cookie": 'tiddlyweb_user="******"; %s' % (user_cookie, csrf_token), }, body="%s&csrf_token=%s" % (data, encode_name(nonce)), ) assert response["status"] == "204", content # test failure response, content = http.request( "http://0.0.0.0:8080/bags/foo_public/tiddlers", method="POST", headers={"Content-type": "application/x-www-form-urlencoded", "Cookie": 'tiddlyweb_user="******"' % user_cookie}, body="%s" % data, ) assert response["status"] == "400", content
def _log_app(self, environ, start_response): req_uri = quote(environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')) if environ.get('QUERY_STRING'): req_uri += '?' + environ['QUERY_STRING'] def replacement_start_response(status, headers, exc_info=None): """ We need to gaze at the content-length, if set, to write log info. """ size = None for name, value in headers: if name.lower() == 'content-length': size = value self.write_log(environ, req_uri, status, size) return start_response(status, headers, exc_info) return self.application(environ, replacement_start_response)
def test_nonce_not_left_over(): """ Test that the nonce is not left over in the tiddler after a POST i.e. check that it is removed before the request continues """ store = get_store(config) hostname = "foo.0.0.0.0:8080" user = User(u"f\u00F6o") user.set_password("foobar") store.put(user) timestamp = datetime.utcnow().strftime("%Y%m%d%H") secret = config["secret"] nonce = "%s:%s:%s" % ( timestamp, user.usersign, sha("%s:%s:%s:%s" % (user.usersign, timestamp, hostname, secret)).hexdigest(), ) user_cookie = get_auth(u"f\u00F6o", "foobar") data = "title=foobar&text=hello%20world&extra_field=baz" nonce = quote(nonce.encode("utf-8"), safe=".!~*'():") # test success response, _ = http.request( "http://foo.0.0.0.0:8080/bags/foo_public/tiddlers", method="POST", headers={"Content-type": "application/x-www-form-urlencoded", "Cookie": 'tiddlyweb_user="******"' % user_cookie}, body="%s&csrf_token=%s" % (data, nonce), ) assert response["status"] == "204" new_tiddler = Tiddler("foobar") new_tiddler.bag = "foo_public" new_tiddler = store.get(new_tiddler) assert new_tiddler.title == "foobar" assert new_tiddler.text == "hello world" assert new_tiddler.fields.get("extra_field") == "baz" assert new_tiddler.fields.get("nonce") is None
def fake_start_response(status, headers, exc_info=None): """ add a csrf_token header (if we need to) """ user_cookie = Cookie.SimpleCookie() user_cookie.load(str(environ.get('HTTP_COOKIE', ''))) csrf_cookie = user_cookie.get('csrf_token') timestamp = '' cookie_user = None if csrf_cookie: try: timestamp, cookie_user, _ = csrf_cookie.value.split(':', 2) cookie_user = unquote(cookie_user) except ValueError: timestamp = '' cookie_user = '' username = environ['tiddlyweb.usersign']['name'] now = datetime.utcnow().strftime('%Y%m%d%H') if username == 'GUEST': if cookie_user: if 'MSIE' in environ.get('HTTP_USER_AGENT', ''): expires = 'Expires=%s;' % datetime.strftime( datetime.utcnow() - timedelta(hours=1), '%a, %d-%m-%y %H:%M:%S GMT') else: expires = 'Max-Age=0;' set_cookie = 'csrf_token=; %s' % expires headers.append(('Set-Cookie', set_cookie)) else: start_response(status, headers, exc_info) return elif now != timestamp or cookie_user != username: user, space, secret = get_nonce_components(environ) nonce = gen_nonce(user, space, now, secret) # URL encode cookie for safe Unicode usernames set_cookie = 'csrf_token=%s' % quote(nonce.encode('utf-8'), safe=".!~*'():") headers.append(('Set-Cookie', set_cookie)) start_response(status, headers, exc_info)
def get_url_handle(url): """ Open the url using urllib2.urlopen. If the url is a filepath transform it into a file url. """ try: try: try: handle = urlopen(url) except (URLError, OSError): (scheme, netloc, path, params, query, fragment) = urlparse(url) path = quote(path) newurl = urlunparse( (scheme, netloc, path, params, query, fragment)) handle = urlopen(newurl) except ValueError: # If ValueError happens again we want it to raise url = 'file://' + os.path.abspath(url) handle = urlopen(url) return url, handle except HTTPError as exc: raise ValueError('%s: %s' % (exc, url))
def recipe_as(self, recipe): """ Dump a :py:class:`recipe <tiddlyweb.model.recipe.Recipe>` as text. """ policy_dict = dict([(key, getattr(recipe.policy, key)) for key in Policy.attributes]) lines = ['desc: %s' % recipe.desc, 'policy: %s' % simplejson.dumps(policy_dict), ''] for bag, filter_string in recipe.get_recipe(): line = '' if not get_bag_retriever(self.environ, bag): # If there is a retriever for this bag name then # we want to write its name straight. line += '/bags/%s/tiddlers' % quote( bag.encode('utf-8'), safe='') else: line += bag if filter_string: line += '?%s' % filter_string lines.append(line) return "\n".join(lines)
def get_url_handle(url): """ Open the url using urllib2.urlopen. If the url is a filepath transform it into a file url. """ try: try: try: handle = urlopen(url) except (URLError, OSError): (scheme, netloc, path, params, query, fragment) = urlparse(url) path = quote(path) newurl = urlunparse((scheme, netloc, path, params, query, fragment)) handle = urlopen(newurl) except ValueError: # If ValueError happens again we want it to raise url = 'file://' + os.path.abspath(url) handle = urlopen(url) return url, handle except HTTPError as exc: raise ValueError('%s: %s' % (exc, url))
def uri(name): """URI escape a name.""" return quote(name.encode('utf-8'), safe='')
def encode_name(name): """ Encode a unicode value as utf-8 and then URL encode that string. Use for entity titles in URLs. """ return quote(name.encode('utf-8'), safe=".!~*'()")
def encode_name(name): """ Like the encode_name found in tiddlyweb, but does not escape #. """ return quote(name.encode('utf-8'), safe=".!~*'()#")
def policy_mgr(environ, start_repsonse): """ Redirect to the policy manager for this tank. """ tank_name = get_route_value(environ, 'bag_name') raise HTTP302('/policymgr#/%s' % quote(tank_name, safe=''))