def _get_wiki_versions(node, name, anonymous=False): key = to_mongo_key(name) # Skip if wiki_page doesn't exist; happens on new projects before # default "home" page is created if key not in node.wiki_pages_versions: return [] versions = [ NodeWikiPage.load(version_wiki_id) for version_wiki_id in node.wiki_pages_versions[key] ] return [{ 'version': version.version, 'user_fullname': privacy_info_handle(version.user.fullname, anonymous, name=True), 'date': version.date.replace(microsecond=0), 'compare_web_url': node.web_url_for('project_wiki_compare', wname=name, wver=version.version, _guid=True), } for version in reversed(versions)]
def test_wiki_page_name_non_ascii(self): project = ProjectFactory(creator=self.user) non_ascii = to_mongo_key('WöRlÐé') self.app.get('/{0}/wiki/{1}/'.format(project._primary_key, non_ascii), auth=self.auth, expect_errors=True) project.update_node_wiki(non_ascii, 'new content', Auth(self.user)) assert_in(non_ascii, project.wiki_pages_current)
def test_project_wiki_delete_w_valid_special_characters(self): # TODO: Need to understand why calling update_node_wiki with failure causes transaction rollback issue later # with assert_raises(NameInvalidError): # self.project.update_node_wiki(SPECIAL_CHARACTERS_ALL, 'Hello Special Characters', self.consolidate_auth) self.project.update_node_wiki(SPECIAL_CHARACTERS_ALLOWED, 'Hello Special Characters', self.consolidate_auth) self.special_characters_wiki = self.project.get_wiki_page( SPECIAL_CHARACTERS_ALLOWED) assert_in(to_mongo_key(SPECIAL_CHARACTERS_ALLOWED), self.project.wiki_pages_current) url = self.project.api_url_for('project_wiki_delete', wname=SPECIAL_CHARACTERS_ALLOWED) self.app.delete(url, auth=self.auth) self.project.reload() assert_not_in(to_mongo_key(SPECIAL_CHARACTERS_ALLOWED), self.project.wiki_pages_current)
def test_cannot_rename_wiki_page_to_home(self): user = AuthUserFactory() # A fresh project where the 'home' wiki page has no content project = ProjectFactory(creator=user) project.update_node_wiki('Hello', 'hello world', Auth(user=user)) url = project.api_url_for('project_wiki_rename', wname=to_mongo_key('Hello')) res = self.app.put_json(url, {'value': 'home'}, auth=user.auth, expect_errors=True) assert_equal(res.status_code, 409)
def test_project_wiki_delete_w_valid_special_characters(self): # TODO: Need to understand why calling update_node_wiki with failure causes transaction rollback issue later # with assert_raises(NameInvalidError): # self.project.update_node_wiki(SPECIAL_CHARACTERS_ALL, 'Hello Special Characters', self.consolidate_auth) self.project.update_node_wiki(SPECIAL_CHARACTERS_ALLOWED, 'Hello Special Characters', self.consolidate_auth) self.special_characters_wiki = self.project.get_wiki_page(SPECIAL_CHARACTERS_ALLOWED) assert_in(to_mongo_key(SPECIAL_CHARACTERS_ALLOWED), self.project.wiki_pages_current) url = self.project.api_url_for( 'project_wiki_delete', wname=SPECIAL_CHARACTERS_ALLOWED ) self.app.delete( url, auth=self.auth ) self.project.reload() assert_not_in(to_mongo_key(SPECIAL_CHARACTERS_ALLOWED), self.project.wiki_pages_current)
def get_sharejs_uuid(node, wname): """ Format private uuid into the form used in mongo and sharejs. This includes node's primary ID to prevent fork namespace collision """ wiki_key = to_mongo_key(wname) private_uuid = node.wiki_private_uuids.get(wiki_key) return str(uuid.uuid5(uuid.UUID(private_uuid), str( node._id))) if private_uuid else None
def test_wiki_page_name_non_ascii(self): project = ProjectFactory(creator=self.user) non_ascii = to_mongo_key('WöRlÐé') self.app.get('/{0}/wiki/{1}/'.format( project._primary_key, non_ascii ), auth=self.auth, expect_errors=True) project.update_node_wiki(non_ascii, 'new content', Auth(self.user)) assert_in(non_ascii, project.wiki_pages_current)
def project_wiki_validate_name(wname, **kwargs): node = kwargs['node'] or kwargs['project'] wiki_name = wname.strip() wiki_key = to_mongo_key(wiki_name) if wiki_key in node.wiki_pages_current or wiki_key == 'home': raise HTTPError(http.CONFLICT, data=dict( message_short='Wiki page name conflict.', message_long='A wiki page with that name already exists.' )) return {'message': wiki_name}
def get_sharejs_uuid(node, wname): """ Format private uuid into the form used in mongo and sharejs. This includes node's primary ID to prevent fork namespace collision """ wiki_key = to_mongo_key(wname) private_uuid = node.wiki_private_uuids.get(wiki_key) return str(uuid.uuid5( uuid.UUID(private_uuid), str(node._id) )) if private_uuid else None
def delete_share_doc(node, wname): """Deletes share document and removes namespace from model.""" db = share_db() sharejs_uuid = get_sharejs_uuid(node, wname) db['docs'].remove({'_id': sharejs_uuid}) db['docs_ops'].remove({'name': sharejs_uuid}) wiki_key = to_mongo_key(wname) del node.wiki_private_uuids[wiki_key] node.save()
def generate_private_uuid(node, wname): """ Generate private uuid for internal use in sharejs namespacing. Note that this will NEVER be passed to to the client or sharejs. """ private_uuid = str(uuid.uuid1()) wiki_key = to_mongo_key(wname) node.wiki_private_uuids[wiki_key] = private_uuid node.save() return private_uuid
def test_generate_share_uuid(self): wname = 'bar.baz' wkey = to_mongo_key(wname) assert_is_none(self.project.wiki_private_uuids.get(wkey)) share_uuid = generate_private_uuid(self.project, wname) self.project.reload() assert_equal(self.project.wiki_private_uuids[wkey], share_uuid) new_uuid = generate_private_uuid(self.project, wname) self.project.reload() assert_not_equal(share_uuid, new_uuid) assert_equal(self.project.wiki_private_uuids[wkey], new_uuid)
def project_wiki_validate_name(wname, auth, node, **kwargs): wiki_name = wname.strip() wiki_key = to_mongo_key(wiki_name) if wiki_key in node.wiki_pages_current or wiki_key == "home": raise HTTPError( http.CONFLICT, data=dict( message_short="Wiki page name conflict.", message_long="A wiki page with that name already exists." ), ) else: node.update_node_wiki(wiki_name, "", auth) return {"message": wiki_name}
def test_migrate_uuid_no_mongo(self, mock_sharejs): # Case where no edits have been made to the wiki wname = 'bar.baz' wkey = to_mongo_key(wname) share_uuid = generate_private_uuid(self.project, wname) sharejs_uuid = get_sharejs_uuid(self.project, wname) self.project.update_node_wiki(wname, 'Hello world', Auth(self.user)) wiki_page = self.project.get_wiki_page(wname) migrate_uuid(self.project, wname) assert_not_equal(share_uuid, self.project.wiki_private_uuids.get(wkey)) assert_is_none(self.db.docs.find_one({'_id': sharejs_uuid})) assert_is_none(self.db.docs_ops.find_one({'name': sharejs_uuid}))
def _get_wiki_versions(node, name, anonymous=False): key = to_mongo_key(name) # Skip if wiki_page doesn't exist; happens on new projects before # default "home" page is created if key not in node.wiki_pages_versions: return [] versions = [NodeWikiPage.load(version_wiki_id) for version_wiki_id in node.wiki_pages_versions[key]] return [ { "version": version.version, "user_fullname": privacy_info_handle(version.user.fullname, anonymous, name=True), "date": version.date.replace(microsecond=0).isoformat(), } for version in reversed(versions) ]
def _get_wiki_versions(node, name, anonymous=False): key = to_mongo_key(name) # Skip if wiki_page doesn't exist; happens on new projects before # default "home" page is created if key not in node.wiki_pages_versions: return [] versions = [ NodeWikiPage.load(version_wiki_id) for version_wiki_id in node.wiki_pages_versions[key] ] return [ { 'version': version.version, 'user_fullname': privacy_info_handle(version.user.fullname, anonymous, name=True), 'date': '{} UTC'.format(version.date.replace(microsecond=0).isoformat().replace('T', ' ')), } for version in reversed(versions) ]
def _get_wiki_versions(node, name, anonymous=False): key = to_mongo_key(name) # Skip if wiki_page doesn't exist; happens on new projects before # default "home" page is created if key not in node.wiki_pages_versions: return [] versions = [ NodeWikiPage.load(version_wiki_id) for version_wiki_id in node.wiki_pages_versions[key] ] return [ { 'version': version.version, 'user_fullname': privacy_info_handle(version.user.fullname, anonymous, name=True), 'date': version.date.replace(microsecond=0), 'compare_web_url': node.web_url_for('project_wiki_compare', wname=name, wver=version.version, _guid=True), } for version in reversed(versions) ]
def setUp(self): super(TestWikiShareJSMongo, self).setUp() self.user = AuthUserFactory() self.project = ProjectFactory(is_public=True, creator=self.user) self.wname = 'foo.bar' self.wkey = to_mongo_key(self.wname) self.private_uuid = generate_private_uuid(self.project, self.wname) self.sharejs_uuid = get_sharejs_uuid(self.project, self.wname) # Create wiki page self.project.update_node_wiki(self.wname, 'Hello world', Auth(self.user)) self.wiki_page = self.project.get_wiki_page(self.wname) # Insert mongo data for current project/wiki self.db = share_db() example_uuid = EXAMPLE_DOCS[0]['_id'] self.example_docs = deepcopy(EXAMPLE_DOCS) self.example_docs[0]['_id'] = self.sharejs_uuid self.db.docs.insert(self.example_docs) self.example_ops = deepcopy(EXAMPLE_OPS) for item in self.example_ops: item['_id'] = item['_id'].replace(example_uuid, self.sharejs_uuid) item['name'] = item['name'].replace(example_uuid, self.sharejs_uuid) self.db.docs_ops.insert(self.example_ops)
def test_uuid_persists_after_rename(self, mock_sharejs): new_wname = 'bar.baz' new_wkey = to_mongo_key(new_wname) assert_is_none(self.project.wiki_private_uuids.get(self.wkey)) assert_is_none(self.project.wiki_private_uuids.get(new_wkey)) # Create wiki page self.project.update_node_wiki(self.wname, 'Hello world', Auth(self.user)) wiki_page = self.project.get_wiki_page(self.wname) # Visit wiki edit page original_edit_url = self.project.web_url_for('project_wiki_view', wname=self.wname) res = self.app.get(original_edit_url, auth=self.user.auth) assert_equal(res.status_code, 200) self.project.reload() original_private_uuid = self.project.wiki_private_uuids.get(self.wkey) original_sharejs_uuid = get_sharejs_uuid(self.project, self.wname) # Rename wiki rename_url = self.project.api_url_for('project_wiki_rename', wname=self.wname) res = self.app.put_json( rename_url, {'value': new_wname, 'pk': wiki_page._id}, auth=self.user.auth, ) assert_equal(res.status_code, 200) self.project.reload() assert_is_none(self.project.wiki_private_uuids.get(self.wkey)) assert_equal(original_private_uuid, self.project.wiki_private_uuids.get(new_wkey)) # Revisit original wiki edit page res = self.app.get(original_edit_url, auth=self.user.auth) assert_equal(res.status_code, 200) self.project.reload() assert_not_equal(original_private_uuid, self.project.wiki_private_uuids.get(self.wkey)) assert_not_in(original_sharejs_uuid, res.body)
def project_wiki_view(auth, wname, path=None, **kwargs): node = kwargs['node'] or kwargs['project'] anonymous = has_anonymous_link(node, auth) wiki_name = (wname or '').strip() wiki_key = to_mongo_key(wiki_name) wiki_page = node.get_wiki_page(wiki_name) wiki_settings = node.get_addon('wiki') can_edit = ( auth.logged_in and not node.is_registration and ( node.has_permission(auth.user, 'write') or wiki_settings.is_publicly_editable ) ) versions = _get_wiki_versions(node, wiki_name, anonymous=anonymous) # Determine panels used in view panels = {'view', 'edit', 'compare', 'menu'} if request.args and set(request.args).intersection(panels): panels_used = [panel for panel in request.args if panel in panels] num_columns = len(set(panels_used).intersection({'view', 'edit', 'compare'})) if num_columns == 0: panels_used.append('view') num_columns = 1 else: panels_used = ['view', 'menu'] num_columns = 1 try: view = wiki_utils.format_wiki_version( version=request.args.get('view'), num_versions=len(versions), allow_preview=True, ) compare = wiki_utils.format_wiki_version( version=request.args.get('compare'), num_versions=len(versions), allow_preview=False, ) except InvalidVersionError: raise WIKI_INVALID_VERSION_ERROR # Default versions for view and compare version_settings = { 'view': view or ('preview' if 'edit' in panels_used else 'current'), 'compare': compare or 'previous', } # ensure home is always lower case since it cannot be renamed if wiki_name.lower() == 'home': wiki_name = 'home' if wiki_page: version = wiki_page.version is_current = wiki_page.is_current content = wiki_page.html(node) use_python_render = wiki_page.rendered_before_update else: version = 'NA' is_current = False content = '' use_python_render = False if can_edit: if wiki_key not in node.wiki_private_uuids: wiki_utils.generate_private_uuid(node, wiki_name) sharejs_uuid = wiki_utils.get_sharejs_uuid(node, wiki_name) else: if wiki_key not in node.wiki_pages_current and wiki_key != 'home': raise WIKI_PAGE_NOT_FOUND_ERROR if 'edit' in request.args: if wiki_settings.is_publicly_editable: raise HTTPError(http.UNAUTHORIZED) raise HTTPError(http.FORBIDDEN) sharejs_uuid = None # Opens 'edit' panel when home wiki is empty if not content and can_edit and wiki_name == 'home': panels_used.append('edit') ret = { 'wiki_id': wiki_page._primary_key if wiki_page else None, 'wiki_name': wiki_page.page_name if wiki_page else wiki_name, 'wiki_content': content, 'use_python_render': use_python_render, 'page': wiki_page, 'version': version, 'versions': versions, 'sharejs_uuid': sharejs_uuid or '', 'sharejs_url': settings.SHAREJS_URL, 'is_current': is_current, 'version_settings': version_settings, 'pages_current': _get_wiki_pages_current(node), 'category': node.category, 'panels_used': panels_used, 'num_columns': num_columns, 'urls': { 'api': _get_wiki_api_urls(node, wiki_name, { 'content': node.api_url_for('wiki_page_content', wname=wiki_name), 'draft': node.api_url_for('wiki_page_draft', wname=wiki_name), }), 'web': _get_wiki_web_urls(node, wiki_name), 'gravatar': get_gravatar(auth.user, 25), }, } ret.update(_view_project(node, auth, primary=True)) ret['user']['can_edit_wiki_body'] = can_edit return ret
def project_wiki_view(auth, wname, path=None, **kwargs): node = kwargs["node"] or kwargs["project"] anonymous = has_anonymous_link(node, auth) wiki_name = (wname or "").strip() wiki_key = to_mongo_key(wiki_name) wiki_page = node.get_wiki_page(wiki_name) wiki_settings = node.get_addon("wiki") can_edit = ( auth.logged_in and not node.is_registration and (node.has_permission(auth.user, "write") or wiki_settings.is_publicly_editable) ) versions = _get_wiki_versions(node, wiki_name, anonymous=anonymous) # Determine panels used in view panels = {"view", "edit", "compare", "menu"} if request.args and set(request.args).intersection(panels): panels_used = [panel for panel in request.args if panel in panels] num_columns = len(set(panels_used).intersection({"view", "edit", "compare"})) if num_columns == 0: panels_used.append("view") num_columns = 1 else: panels_used = ["view", "menu"] num_columns = 1 try: view = wiki_utils.format_wiki_version( version=request.args.get("view"), num_versions=len(versions), allow_preview=True ) compare = wiki_utils.format_wiki_version( version=request.args.get("compare"), num_versions=len(versions), allow_preview=False ) except InvalidVersionError: raise WIKI_INVALID_VERSION_ERROR # Default versions for view and compare version_settings = { "view": view or ("preview" if "edit" in panels_used else "current"), "compare": compare or "previous", } # ensure home is always lower case since it cannot be renamed if wiki_name.lower() == "home": wiki_name = "home" if wiki_page: version = wiki_page.version is_current = wiki_page.is_current content = wiki_page.html(node) use_python_render = wiki_page.rendered_before_update else: version = "NA" is_current = False content = "" use_python_render = False if can_edit: if wiki_key not in node.wiki_private_uuids: wiki_utils.generate_private_uuid(node, wiki_name) sharejs_uuid = wiki_utils.get_sharejs_uuid(node, wiki_name) else: if wiki_key not in node.wiki_pages_current and wiki_key != "home": raise WIKI_PAGE_NOT_FOUND_ERROR if "edit" in request.args: if wiki_settings.is_publicly_editable: raise HTTPError(http.UNAUTHORIZED) raise HTTPError(http.FORBIDDEN) sharejs_uuid = None ret = { "wiki_id": wiki_page._primary_key if wiki_page else None, "wiki_name": wiki_page.page_name if wiki_page else wiki_name, "wiki_content": content, "use_python_render": use_python_render, "page": wiki_page, "version": version, "versions": versions, "sharejs_uuid": sharejs_uuid or "", "sharejs_url": settings.SHAREJS_URL, "is_current": is_current, "version_settings": version_settings, "pages_current": _get_wiki_pages_current(node), "category": node.category, "panels_used": panels_used, "num_columns": num_columns, "urls": { "api": _get_wiki_api_urls( node, wiki_name, { "content": node.api_url_for("wiki_page_content", wname=wiki_name), "draft": node.api_url_for("wiki_page_draft", wname=wiki_name), }, ), "web": _get_wiki_web_urls(node, wiki_name), "gravatar": get_gravatar(auth.user, 25), }, } ret.update(_view_project(node, auth, primary=True)) ret["user"]["can_edit_wiki_body"] = can_edit return ret
def is_current(self): key = to_mongo_key(self.page_name) if key in self.node.wiki_pages_current: return self.node.wiki_pages_current[key] == self._id else: return False
def is_deleted(self): key = mongo_utils.to_mongo_key(self.page_name) return key not in self.node.wiki_pages_current
def setUp(self): super(TestWikiUuid, self).setUp() self.user = AuthUserFactory() self.project = ProjectFactory(is_public=True, creator=self.user) self.wname = 'foo.bar' self.wkey = to_mongo_key(self.wname)
def project_wiki_view(auth, wname, path=None, **kwargs): node = kwargs['node'] or kwargs['project'] anonymous = has_anonymous_link(node, auth) wiki_name = (wname or '').strip() wiki_key = to_mongo_key(wiki_name) wiki_page = node.get_wiki_page(wiki_name) wiki_settings = node.get_addon('wiki') can_edit = ( auth.logged_in and not node.is_registration and ( node.has_permission(auth.user, 'write') or wiki_settings.is_publicly_editable ) ) versions = _get_wiki_versions(node, wiki_name, anonymous=anonymous) # Determine panels used in view panels = {'view', 'edit', 'compare', 'menu'} if request.args and set(request.args).intersection(panels): panels_used = [panel for panel in request.args if panel in panels] num_columns = len(set(panels_used).intersection({'view', 'edit', 'compare'})) if num_columns == 0: panels_used.append('view') num_columns = 1 else: panels_used = ['view', 'menu'] num_columns = 1 try: view = wiki_utils.format_wiki_version( version=request.args.get('view'), num_versions=len(versions), allow_preview=True, ) compare = wiki_utils.format_wiki_version( version=request.args.get('compare'), num_versions=len(versions), allow_preview=False, ) except InvalidVersionError: raise WIKI_INVALID_VERSION_ERROR # ensure home is always lower case since it cannot be renamed if wiki_name.lower() == 'home': wiki_name = 'home' if wiki_page: version = wiki_page.version is_current = wiki_page.is_current content = wiki_page.html(node) rendered_before_update = wiki_page.rendered_before_update else: version = 'NA' is_current = False content = '' rendered_before_update = False if can_edit: if wiki_key not in node.wiki_private_uuids: wiki_utils.generate_private_uuid(node, wiki_name) sharejs_uuid = wiki_utils.get_sharejs_uuid(node, wiki_name) else: if wiki_key not in node.wiki_pages_current and wiki_key != 'home': raise WIKI_PAGE_NOT_FOUND_ERROR if 'edit' in request.args: if wiki_settings.is_publicly_editable: raise HTTPError(http.UNAUTHORIZED) raise HTTPError(http.FORBIDDEN) sharejs_uuid = None # Opens 'edit' panel when home wiki is empty if not content and can_edit and wiki_name == 'home': panels_used.append('edit') # Default versions for view and compare version_settings = { 'view': view or ('preview' if 'edit' in panels_used else 'current'), 'compare': compare or 'previous', } ret = { 'wiki_id': wiki_page._primary_key if wiki_page else None, 'wiki_name': wiki_page.page_name if wiki_page else wiki_name, 'wiki_content': content, 'rendered_before_update': rendered_before_update, 'page': wiki_page, 'version': version, 'versions': versions, 'sharejs_uuid': sharejs_uuid or '', 'sharejs_url': settings.SHAREJS_URL, 'is_current': is_current, 'version_settings': version_settings, 'pages_current': _get_wiki_pages_current(node), 'category': node.category, 'panels_used': panels_used, 'num_columns': num_columns, 'urls': { 'api': _get_wiki_api_urls(node, wiki_name, { 'content': node.api_url_for('wiki_page_content', wname=wiki_name), 'draft': node.api_url_for('wiki_page_draft', wname=wiki_name), }), 'web': _get_wiki_web_urls(node, wiki_name), 'gravatar': get_gravatar(auth.user, 25), }, } ret.update(_view_project(node, auth, primary=True)) ret['user']['can_edit_wiki_body'] = can_edit return ret