def get(environ, start_response): """ Perform a search on the store. What search means and what results are returned is dependent on the search implementation (if any) in the chosen store. """ store = environ['tiddlyweb.store'] search_query = get_search_query(environ) title = 'Search for %s' % search_query title = environ['tiddlyweb.query'].get('title', [title])[0] try: tiddlers = get_tiddlers(environ) usersign = environ['tiddlyweb.usersign'] candidate_tiddlers = Tiddlers(title=title, store=store) candidate_tiddlers.is_search = True for tiddler in readable_tiddlers_by_bag(store, tiddlers, usersign): candidate_tiddlers.add(tiddler) except StoreMethodNotImplemented: raise HTTP400('Search system not implemented') except StoreError, exc: raise HTTP400('Error while processing search: %s' % exc)
def _create_bag(environ): """Take the form input and turn it into a bag.""" query_data = _flatten_form_data(environ['tiddlyweb.query']) logging.debug(query_data) store = environ['tiddlyweb.store'] try: new_bag_name = query_data['bag_name'] if _bag_exists(store, new_bag_name): raise HTTP409('That bag may not be created.') new_bag = Bag(new_bag_name) username = environ['tiddlyweb.usersign']['name'] new_bag.policy.owner = username new_bag.policy.manage = [username] new_bag.desc = query_data.get('bag_desc', '') for policy_type in ('read', 'write', 'create', 'delete'): texted = query_data.get(policy_type + '_text', None) logging.debug('texted: %s' % texted) if texted: new_bag.policy.__setattr__( policy_type, [x.lstrip().rstrip() for x in texted.split(',')]) else: set = query_data[policy_type] new_bag.policy.__setattr__( policy_type, _policy_form_to_entry(username, set)) store.put(new_bag) except KeyError, exc: raise HTTP400('something went wrong processing for: %s' % exc)
def _create_recipe(environ): """Take the form input and turn it into a recipe.""" # get bag_names before we flatten because it will be a list bag_names = environ['tiddlyweb.query'].get('bags', []) query_data = _flatten_form_data(environ['tiddlyweb.query']) store = environ['tiddlyweb.store'] try: new_recipe_name = query_data['recipe_name'] if _recipe_exists(store, new_recipe_name): raise HTTP409('That recipe may not be created.') new_recipe = Recipe(new_recipe_name) username = environ['tiddlyweb.usersign']['name'] new_recipe.policy.owner = username new_recipe.policy.manage = [username] new_recipe.desc = query_data.get('recipe_desc', '') privacy = query_data['privacy'] new_recipe.policy.read = _policy_form_to_entry(username, privacy) bag_list = [] if query_data.get('autowiki', 0): bag_list.extend(AUTOWIKI_BAGS) # don't worry about default content bag yet bag_list.extend(bag_names) recipe_list = [[bag_name, ''] for bag_name in bag_list] new_recipe.set_recipe(recipe_list) store.put(new_recipe) except KeyError, exc: raise HTTP400('something went wrong processing for: %s' % exc)
def send_tiddlers(environ, start_response, tiddlers=None): """ Output the tiddlers contained in the provided Tiddlers collection in a Negotiated representation. Often, but not always, a wiki. """ last_modified = None etag = None download = environ['tiddlyweb.query'].get('download', [None])[0] filters = environ['tiddlyweb.filters'] store = environ['tiddlyweb.store'] if filters: candidate_tiddlers = Tiddlers(store=store) try: candidate_tiddlers.title = tiddlers.title candidate_tiddlers.is_search = tiddlers.is_search candidate_tiddlers.is_revisions = tiddlers.is_revisions except AttributeError: pass try: for tiddler in recursive_filter(filters, tiddlers): recipe = tiddler.recipe if not tiddler.store: tiddler = store.get(tiddler) if recipe: tiddler.recipe = recipe candidate_tiddlers.add(tiddler) except FilterError, exc: raise HTTP400('malformed filter: %s' % exc)
def webfinger(environ, start_response): """ Display the webfinger information for a given user. """ http_host, host_url = determine_host(environ) if http_host != host_url: raise HTTP404('No webfinger at this host: %s' % http_host) username = environ['tiddlyweb.query'].get('q', [None])[0] if not username: raise HTTP400('No account provided to webfinger query') if username.startswith('acct:'): username = username.split(':', 1)[1] username = username.split('@', 1)[0] start_response('200 OK', [('Content-Type', 'application/xrd+xml')]) return [ WEBFINGER_TEMPLATE % { 'username': username, 'host': http_host, 'server_host': server_base_url(environ) } ]
def get(environ, start_response): """ Using query parameters, determine the current tiddler and produce an editor for it. """ usersign = environ['tiddlyweb.usersign'] try: tiddler_name = environ['tiddlyweb.query'].get('tiddler', [''])[0] recipe_name = environ['tiddlyweb.query'].get('recipe', [''])[0] bag_name = environ['tiddlyweb.query'].get('bag', [''])[0] except (KeyError, IndexError): raise HTTP400('tiddler, recipe and bag query strings required') store = environ['tiddlyweb.store'] tiddler = Tiddler(tiddler_name) if bag_name: tiddler.bag = bag_name else: recipe = Recipe(recipe_name) try: recipe = store.get(recipe) tiddler.bag = control.determine_tiddler_bag_from_recipe( recipe, tiddler).name tiddler.recipe = recipe.name except NoRecipeError, exc: raise HTTP404('unable to edit %s, recipe %s not found: %s' % (tiddler.title, recipe_name, exc)) except NoBagError, exc: raise HTTP404('unable to edit %s: %s' % (tiddler.title, exc))
def reflect(environ, start_response): if environ.get('tiddlyweb.type', '') == 'multipart/form-data': form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) else: # This hack to ensure that we have a uniform interface # On the cgi form field values whether we are multipart or # url encoded. form = cgi.FieldStorage() form.list = [] for key, value in environ['tiddlyweb.query'].items(): for single_value in value: form.list.append(cgi.MiniFieldStorage(key, single_value)) # Ordering is important here. File will often appear true when # it is not. if 'uri' in form and form['uri'].value: try: uri = form.getfirst('uri') request = urllib2.Request(uri) if (request.get_type() != 'file'): filehandle = urllib2.urlopen(uri) type = filehandle.info()['content-type'] else: raise ValueError('file: not allowed') except (ValueError, AttributeError, urllib2.URLError), exc: raise HTTP400('URI Input error: %s' % exc)
def send_tiddlers(environ, start_response, tiddlers=None): """ Output the tiddlers contained in the provided Tiddlers collection in a Negotiated representation. Often, but not always, a wiki. """ last_modified = None etag = None download = environ['tiddlyweb.query'].get('download', [None])[0] filters = environ['tiddlyweb.filters'] store = environ['tiddlyweb.store'] if tiddlers.store is None and not filters: logging.warn('Incoming tiddlers no store set %s', inspect.stack()[1]) if filters: candidate_tiddlers = Tiddlers(store=store) try: candidate_tiddlers.title = tiddlers.title candidate_tiddlers.link = tiddlers.link candidate_tiddlers.is_search = tiddlers.is_search candidate_tiddlers.is_revisions = tiddlers.is_revisions except AttributeError: pass try: for tiddler in recursive_filter(filters, tiddlers): candidate_tiddlers.add(tiddler) except FilterError, exc: raise HTTP400('malformed filter: %s' % exc)
def delete(environ, start_response): """ Remove a bag and its tiddlers from the store. How the store chooses to handle remove and what it means is up to the store. """ bag_name = web.get_route_value(environ, 'bag_name') bag_name = web.handle_extension(environ, bag_name) usersign = environ['tiddlyweb.usersign'] bag = _get_bag(environ, bag_name) bag.policy.allows(usersign, 'manage') # reuse the store attribute that was set on the # bag when we "got" it. # we don't need to check for existence here because # the above get already did try: store = environ['tiddlyweb.store'] store.delete(bag) except StoreMethodNotImplemented: raise HTTP400('Bag DELETE not supported') start_response("204 No Content", []) return []
def put(environ, start_response): """ Put a bag to the server, meaning the description and policy of the bag, if policy allows. """ bag_name = web.get_route_value(environ, 'bag_name') bag_name = web.handle_extension(environ, bag_name) bag = Bag(bag_name) store = environ['tiddlyweb.store'] length = environ['CONTENT_LENGTH'] usersign = environ['tiddlyweb.usersign'] try: bag = store.get(bag) bag.policy.allows(usersign, 'manage') except NoBagError: create_policy_check(environ, 'bag', usersign) try: serialize_type = web.get_serialize_type(environ)[0] serializer = Serializer(serialize_type, environ) serializer.object = bag content = environ['wsgi.input'].read(int(length)) serializer.from_string(content.decode('utf-8')) bag.policy.owner = usersign['name'] _validate_bag(environ, bag) store.put(bag) except BagFormatError, exc: raise HTTP400('unable to put bag: %s' % exc)
def put_user(environ, start_response): """ Allow a user or an admin to set the password for a user at /users/{usersign}. A non-admin can only set their own password. The sent data is a JSON dict with at least the key 'password' with a value of whatever the password should be. Users of this method should take not that that password is being sent in the clear over what is likely an unencrypted network. """ store = environ['tiddlyweb.store'] current_user = environ['tiddlyweb.usersign'] target_user = environ['wsgiorg.routing_args'][1]['usersign'] target_user = urllib.unquote(target_user) target_user = unicode(target_user, 'utf-8') if not ('ADMIN' in current_user['roles'] or current_user['name'] == target_user): raise HTTP403('Incorrect User') content_type = environ['tiddlyweb.type'] length = environ['CONTENT_LENGTH'] if content_type != 'application/json': raise HTTP415('application/json required') content = environ['wsgi.input'].read(int(length)) try: user_info = simplejson.loads(content) except ValueError, exc: raise HTTP400('Invalid JSON, %s' % exc)
def list_entities(environ, start_response, mime_type, store_list, serializer_list): """ Get a list of all the bags or recipes the current user can read. """ filters = environ['tiddlyweb.filters'] try: kept_entities = _filter_readable(environ, store_list(), filters) except FilterError, exc: raise HTTP400(exc)
def get_search_query(environ): """ Inspect tiddlyweb.query in the environment to get the search query. """ try: search_query = environ['tiddlyweb.query']['q'][0] search_query = urllib.unquote(search_query) except (KeyError, IndexError): raise HTTP400('query string required') return search_query
def _length_and_type(environ): """ To PUT we must have content-length and content-type headers. Raise 400 if we cannot get these things. """ try: length = environ['CONTENT_LENGTH'] content_type = environ['tiddlyweb.type'] except KeyError: raise HTTP400( 'Content-Length and content-type required to put tiddler') return length, content_type
def extract_query(self, environ): """ Read the QUERY_STRING and body (if a POSTed form) to extract query paremeters. Put the results in tiddlyweb.query. """ content_type = environ.get('CONTENT_TYPE', '') environ['tiddlyweb.query'] = {} if environ['REQUEST_METHOD'].upper() == 'POST' and \ content_type.startswith('application/x-www-form-urlencoded'): try: length = environ['CONTENT_LENGTH'] content = environ['wsgi.input'].read(int(length)) except KeyError, exc: raise HTTP400('Invalid post, unable to read content: %s' % exc) posted_data = parse_qs(content, keep_blank_values=True) try: _update_tiddlyweb_query(environ, posted_data) except UnicodeDecodeError, exc: raise HTTP400( 'Invalid encoding in query string, utf-8 required: %s', exc)
def put(environ, start_response): """ Put a new recipe to the server. """ recipe_name = environ['wsgiorg.routing_args'][1]['recipe_name'] recipe_name = urllib.unquote(recipe_name) recipe_name = unicode(recipe_name, 'utf-8') recipe_name = web.handle_extension(environ, recipe_name) recipe = Recipe(recipe_name) store = environ['tiddlyweb.store'] length = environ['CONTENT_LENGTH'] usersign = environ['tiddlyweb.usersign'] try: recipe = store.get(recipe) recipe.policy.allows(usersign, 'manage') except NoRecipeError: create_policy_check(environ, 'recipe', usersign) try: serialize_type = web.get_serialize_type(environ)[0] except TypeError: raise HTTP400('Content-type header required') try: serializer = Serializer(serialize_type, environ) serializer.object = recipe content = environ['wsgi.input'].read(int(length)) serializer.from_string(content.decode('utf-8')) recipe.policy.owner = usersign['name'] _validate_recipe(environ, recipe) store.put(recipe) except RecipeFormatError, exc: raise HTTP400('unable to put recipe: %s' % exc)
def get_route_value(environ, name): """ Retrieve and decode from UTF-8 data provided in WSGI route. If name is not present in the route, allow KeyError to raise. If the provided data is not URI escaped UTF-8, raise and HTTP400 """ try: value = environ['wsgiorg.routing_args'][1][name] value = urllib.unquote(value).decode('utf-8') except UnicodeDecodeError, exc: raise HTTP400('incorrect encoding for %s, UTF-8 required: %s', exc)
def manage_mine(environ, start_response): """Handle POST""" # put a try around this stuff eventually creation = environ['tiddlyweb.query']['creatego'][0] if creation == 'Quick!': _quick_create(environ) elif creation == 'Create Recipe': _create_recipe(environ) elif creation == 'Create Bag': _create_bag(environ) else: raise HTTP400('Improper Form Submit') home_uri = '%s/mine' % environ['tiddlyweb.config']['server_prefix'] raise HTTP302(home_uri)
def __call__(self, environ, start_response): def fake_start_response(status, headers, exc_info=None): """ add a csrf_token header (if we need to) """ if environ['tiddlyweb.usersign']['name'] == 'GUEST': start_response(status, headers, exc_info) return user_cookie = Cookie.SimpleCookie() user_cookie.load(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) except ValueError: timestamp = '' cookie_user = '' username = environ['tiddlyweb.usersign']['name'] now = datetime.now().strftime('%Y%m%d%H') if now != timestamp or cookie_user != username: user, space, secret = get_nonce_components(environ) nonce = gen_nonce(user, space, now, secret) set_cookie = 'csrf_token=%s' % nonce headers.append(('Set-Cookie', set_cookie.encode())) start_response(status, headers, exc_info) def app(): output = self.application(environ, fake_start_response) return output if environ['REQUEST_METHOD'] != 'POST': return app() if environ['tiddlyweb.usersign']['name'] == 'GUEST': return app() if environ.get('tiddlyweb.type', '') not in [ 'application/x-www-form-urlencoded', 'multipart/form-data']: return app() form = environ['tiddlyweb.query'] nonce = form.pop('csrf_token', [''])[0] try: self.check_csrf(environ, nonce) except InvalidNonceError, exc: raise HTTP400(exc)
def delete(environ, start_response): """ Delete a recipe, where what delete means depends on the store used. """ recipe = _determine_recipe(environ) store = environ['tiddlyweb.store'] recipe.policy.allows(environ['tiddlyweb.usersign'], 'manage') try: store.delete(recipe) except StoreMethodNotImplemented: raise HTTP400('Recipe DELETE not supported') start_response("204 No Content", []) return []
def extract(self, environ, start_response): """ Extract the cookie, if there, from the headers and attempt to validate its contents. """ try: user_cookie = environ['HTTP_COOKIE'] logging.debug('simple_cookie looking at cookie string: %s', user_cookie) cookie = Cookie.SimpleCookie() cookie.load(user_cookie) cookie_value = cookie['tiddlyweb_user'].value secret = environ['tiddlyweb.config']['secret'] usersign, cookie_secret = cookie_value.rsplit(':', 1) store = environ['tiddlyweb.store'] if cookie_secret == sha('%s%s' % (usersign, secret)).hexdigest(): usersign = usersign.decode('utf-8') user = self.load_user(environ, usersign) return {"name": user.usersign, "roles": user.list_roles()} except Cookie.CookieError, exc: raise HTTP400('malformed cookie: %s' % exc)
def post_user(environ, start_response): """ Create a new user through a JSON POST to /users. If the not JSON, return 415. If users exists, return 409. The JSON should be a dict with two keys: 'username' and 'password'. Future iterations of this code will take additional keys and save them as fields to be used with the tiddlywebplugins.magicuser extractor. """ content_type = environ['tiddlyweb.type'] if content_type != 'application/json': raise HTTP415('application/json required') length = environ['CONTENT_LENGTH'] content = environ['wsgi.input'].read(int(length)) store = environ['tiddlyweb.store'] try: user_info = simplejson.loads(content) except ValueError, exc: raise HTTP400('Invalid JSON, %s' % exc)
bag.policy.allows(usersign, 'read') except NoBagError, exc: raise HTTP404('recipe %s lists an unknown bag: %s' % (recipe.name, exc)) # from this point forward we know the tiddlers are # readable # get the tiddlers from the recipe and uniquify them try: candidate_tiddlers = control.get_tiddlers_from_recipe(recipe, environ) except NoBagError, exc: raise HTTP404('recipe %s lists an unknown bag: %s' % (recipe.name, exc)) except FilterError, exc: raise HTTP400('malformed filter: %s' % exc) if filters: tiddlers = Tiddlers(title=title) else: tiddlers = Tiddlers(title=title, store=store) for tiddler in candidate_tiddlers: tiddler.recipe = recipe.name tiddlers.add(tiddler) tiddlers.link = '%s/tiddlers' % web.recipe_url(environ, recipe, full=False) return send_tiddlers(environ, start_response, tiddlers=tiddlers)
if environ['REQUEST_METHOD'].upper() == 'POST' and \ content_type.startswith('application/x-www-form-urlencoded'): try: length = environ['CONTENT_LENGTH'] content = environ['wsgi.input'].read(int(length)) except KeyError, exc: raise HTTP400('Invalid post, unable to read content: %s' % exc) posted_data = parse_qs(content, keep_blank_values=True) try: _update_tiddlyweb_query(environ, posted_data) except UnicodeDecodeError, exc: raise HTTP400( 'Invalid encoding in query string, utf-8 required: %s', exc) filters, leftovers = parse_for_filters(environ.get('QUERY_STRING', ''), environ) query_data = parse_qs(leftovers, keep_blank_values=True) try: _update_tiddlyweb_query(environ, query_data) except UnicodeDecodeError, exc: raise HTTP400( 'Invalid encoding in query string, utf-8 required: %s', exc) environ['tiddlyweb.filters'] = filters def _update_tiddlyweb_query(environ, data): environ['tiddlyweb.query'].update( dict([(unicode(key, 'UTF-8'), [unicode(value, 'UTF-8') for value in values]) for key, values in data.items()]))
def extract(self, environ, start_response): """ Extract the cookie, if there, from the headers and attempt to validate its contents. """ try: user_cookie = environ['HTTP_COOKIE'] logging.debug('simple_cookie looking at cookie string: %s', user_cookie) cookie = Cookie.SimpleCookie() cookie.load(user_cookie) cookie_value = cookie['tiddlyweb_user'].value secret = environ['tiddlyweb.config']['secret'] usersign, cookie_secret = cookie_value.rsplit(':', 1) store = environ['tiddlyweb.store'] if cookie_secret == sha('%s%s' % (usersign, secret)).hexdigest(): user = User(usersign) try: user = store.get(user) except (StoreMethodNotImplemented, NoUserError): pass #check that the user has the requisite bags #if they don't, create them public_bag = '%s_public' % user.usersign private_bag = '%s_private' % user.usersign space = { 'bags': { public_bag: { 'policy': { "read": [], "create": [user.usersign], "manage": [user.usersign, "R:ADMIN"], "accept": [], "write": [user.usersign], "owner": user.usersign, "delete": [user.usersign, "R:ADMIN"] } }, private_bag: { 'policy': { "read": [user.usersign], "create": [user.usersign], "manage": [user.usersign, "R:ADMIN"], "accept": [], "write": [user.usersign], "owner": user.usersign, "delete": [user.usersign] } } }, 'recipes': { '%s' % user.usersign: { 'recipe': [['system', ''], [public_bag, ''], [private_bag, '']], 'policy': { "read": [user.usersign], "create": [user.usersign], "manage": [user.usersign, "R:ADMIN"], "accept": [], "write": [user.usersign], "owner": user.usersign, "delete": [user.usersign] } } } } user_space = Space(environ) user_space.create_space(space) return {"name": user.usersign, "roles": user.list_roles()} except Cookie.CookieError, exc: raise HTTP400('malformed cookie: %s' % exc)
raise HTTP400('Content-type header required') try: serializer = Serializer(serialize_type, environ) serializer.object = recipe content = environ['wsgi.input'].read(int(length)) serializer.from_string(content.decode('utf-8')) recipe.policy.owner = usersign['name'] _validate_recipe(environ, recipe) store.put(recipe) except RecipeFormatError, exc: raise HTTP400('unable to put recipe: %s' % exc) except TypeError, exc: raise HTTP400('malformed input: %s' % exc) except NoSerializationError: raise HTTP415('Content type %s not supported' % serialize_type) start_response("204 No Content", [('Location', web.recipe_url(environ, recipe))]) return [] def _validate_recipe(environ, recipe): """ Unless recipe is valid raise a 409 with the reason why. """ try: validate_recipe(recipe, environ)
content = environ['wsgi.input'].read(int(length)) try: try: # XXX HACK! We don't want to decode content unless # the serializer has a as_tiddler. We should be able # to just rely on NoSerializationError, but we need # to call the method to do that, and to call the method we # need to decode the string... serialize_type = web.get_serialize_type(environ)[0] serializer = Serializer(serialize_type, environ) serializer.object = tiddler try: serializer.from_string(content.decode('utf-8')) except TiddlerFormatError, exc: raise HTTP400('unable to put tiddler: %s' % exc) except NoSerializationError: tiddler.type = content_type if pseudo_binary(tiddler.type): tiddler.text = content.decode('utf-8') else: tiddler.text = content except UnicodeDecodeError, exc: raise HTTP400('unable to decode tiddler, utf-8 expected: %s', exc) try: recipe_name = web.get_route_value(environ, 'recipe_name') recipe = Recipe(recipe_name) try: store = environ['tiddlyweb.store'] recipe = store.get(recipe)
content = environ['wsgi.input'].read(int(length)) try: user_info = simplejson.loads(content) except ValueError, exc: raise HTTP400('Invalid JSON, %s' % exc) try: user = User(target_user) try: store.get(user) except NoUserError: raise HTTP404() user.set_password(user_info['password']) except KeyError, exc: raise HTTP400('Missing required data: %s', exc) store.put(user) start_response('204 No Content', [('Content-Type', 'text/html; charset=UTF-8')]) return ['Updated %s' % target_user] def post_user(environ, start_response): """ Create a new user through a JSON POST to /users. If the not JSON, return 415. If users exists, return 409. The JSON should be a dict with two keys: 'username' and 'password'. Future iterations of this code will take additional keys and save them as fields
form.list.append(cgi.MiniFieldStorage(key, single_value)) # Ordering is important here. File will often appear true when # it is not. if 'uri' in form and form['uri'].value: try: uri = form.getfirst('uri') request = urllib2.Request(uri) if (request.get_type() != 'file'): filehandle = urllib2.urlopen(uri) type = filehandle.info()['content-type'] else: raise ValueError('file: not allowed') except (ValueError, AttributeError, urllib2.URLError), exc: raise HTTP400('URI Input error: %s' % exc) elif 'file' in form and form['file'].file: try: filehandle = form['file'].file type = form['file'].type # XXX: this might have charset in it #options = form['file'].type_options except (AttributeError, ValueError), exc: raise HTTP400('File Input error: %s' % exc) else: raise HTTP400('Incomplete form') start_response('200 OK', [('Content-Type', type)]) # XXX If reading the file causes an exception # it will be trapped as a 500 by HTTPExceptor. # Better handling may be desired. return filehandle
try: serialize_type = web.get_serialize_type(environ)[0] serializer = Serializer(serialize_type, environ) serializer.object = bag content = environ['wsgi.input'].read(int(length)) serializer.from_string(content.decode('utf-8')) bag.policy.owner = usersign['name'] _validate_bag(environ, bag) store.put(bag) except BagFormatError, exc: raise HTTP400('unable to put bag: %s' % exc) except TypeError: raise HTTP400('Content-type header required') except NoSerializationError: raise HTTP415('Content type not supported: %s' % serialize_type) start_response("204 No Content", [('Location', web.bag_url(environ, bag))]) return [] def _validate_bag(environ, bag): """ Unless bag is valid raise a 409 with the reason why. """ try: validate_bag(bag, environ)