def test_user_deletes_other_comments(test_app): user_a = fixture_add_user(u"chris_a") user_b = fixture_add_user(u"chris_b") media_a = fixture_media_entry(uploader=user_a.id, save=False, expunge=False, fake_upload=False) media_b = fixture_media_entry(uploader=user_b.id, save=False, expunge=False, fake_upload=False) Session.add(media_a) Session.add(media_b) Session.flush() # Create all 4 possible comments: for u_id in (user_a.id, user_b.id): for m_id in (media_a.id, media_b.id): cmt = MediaComment() cmt.media_entry = m_id cmt.author = u_id cmt.content = u"Some Comment" Session.add(cmt) Session.flush() usr_cnt1 = User.query.count() med_cnt1 = MediaEntry.query.count() cmt_cnt1 = MediaComment.query.count() User.query.get(user_a.id).delete(commit=False) usr_cnt2 = User.query.count() med_cnt2 = MediaEntry.query.count() cmt_cnt2 = MediaComment.query.count() # One user deleted assert usr_cnt2 == usr_cnt1 - 1 # One media gone assert med_cnt2 == med_cnt1 - 1 # Three of four comments gone. assert cmt_cnt2 == cmt_cnt1 - 3 User.query.get(user_b.id).delete() usr_cnt2 = User.query.count() med_cnt2 = MediaEntry.query.count() cmt_cnt2 = MediaComment.query.count() # All users gone assert usr_cnt2 == usr_cnt1 - 2 # All media gone assert med_cnt2 == med_cnt1 - 2 # All comments gone assert cmt_cnt2 == cmt_cnt1 - 4
def fixture_add_comment(author=None, media_entry=None, comment=None): if author is None: author = fixture_add_user().id if media_entry is None: media_entry = fixture_media_entry().id if comment is None: comment = \ 'Auto-generated test comment by user #{0} on media #{0}'.format( author, media_entry) comment = MediaComment(author=author, media_entry=media_entry, content=comment) comment.save() Session.expunge(comment) return comment
def feed(request): """ Handles the user's outbox - /api/user/<username>/feed """ user = request.matchdict["username"] requested_user = User.query.filter_by(username=user).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(user), 404) if request.data: data = json.loads(request.data) else: data = {"verb": None, "object": {}} # We need to check that the user they're posting to is # the person that they are. if request.method in ["POST", "PUT"] and \ requested_user.id != request.user.id: return json_error( "Not able to post to another users feed.", status=403 ) if request.method == "POST" and data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment(author=request.user.id) comment.unserialize(data["object"]) comment.save() data = {"verb": "post", "object": comment.serialize(request)} return json_response(data) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = int(data["object"]["id"]) media = MediaEntry.query.filter_by(id=media_id).first() if media is None: return json_response( "No such 'image' with id '{0}'".format(id=media_id), status=404 ) if not media.unserialize(data["object"]): return json_error( "Invalid 'image' with id '{0}'".format(media_id) ) media.save() api_add_to_feed(request, media) return json_response({ "verb": "post", "object": media.serialize(request) }) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error("Unknown object type '{0}'.".format(object_type)) elif request.method in ["PUT", "POST"] and data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = obj["id"] # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment.query.filter_by(id=obj_id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if comment.author != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403 ) if not comment.unserialize(data["object"]): return json_error( "Invalid 'comment' with id '{0}'".format(obj_id) ) comment.save() activity = { "verb": "update", "object": comment.serialize(request), } return json_response(activity) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(id=obj_id).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if image.uploader != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403 ) if not image.unserialize(obj): return json_error( "Invalid 'image' with id '{0}'".format(obj_id) ) image.save() activity = { "verb": "update", "object": image.serialize(request), } return json_response(activity) elif request.method != "GET": return json_error( "Unsupported HTTP method {0}".format(request.method), status=501 ) feed_url = request.urlgen( "mediagoblin.federation.feed", username=request.user.username, qualified=True ) feed = { "displayName": "Activities by {user}@{host}".format( user=request.user.username, host=request.host ), "objectTypes": ["activity"], "url": feed_url, "links": { "first": { "href": feed_url, }, "self": { "href": request.url, }, "prev": { "href": feed_url, }, "next": { "href": feed_url, } }, "author": request.user.serialize(request), "items": [], } # Look up all the media to put in the feed (this will be changed # when we get real feeds/inboxes/outboxes/activites) for media in MediaEntry.query.all(): item = { "verb": "post", "object": media.serialize(request), "actor": request.user.serialize(request), "content": "{0} posted a picture".format(request.user.username), "id": 1, } item["updated"] = item["object"]["updated"] item["published"] = item["object"]["published"] item["url"] = item["object"]["url"] feed["items"].append(item) feed["totalItems"] = len(feed["items"]) return json_response(feed)
def feed_endpoint(request, outbox=None): """ Handles the user's outbox - /api/user/<username>/feed """ username = request.matchdict["username"] requested_user = User.query.filter_by(username=username).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) if request.data: data = json.loads(request.data.decode()) else: data = {"verb": None, "object": {}} if request.method in ["POST", "PUT"]: # Validate that the activity is valid if "verb" not in data or "object" not in data: return json_error("Invalid activity provided.") # Check that the verb is valid if data["verb"] not in ["post", "update", "delete"]: return json_error("Verb not yet implemented", 501) # We need to check that the user they're posting to is # the person that they are. if requested_user.id != request.user.id: return json_error("Not able to post to another users feed.", status=403) # Handle new posts if data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403) comment = MediaComment(author=request.user.id) comment.unserialize(data["object"], request) comment.save() # Create activity for comment generator = create_generator(request) activity = create_activity(verb="post", actor=request.user, obj=comment, target=comment.get_entry, generator=generator) return json_response(activity.serialize(request)) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = int( extract_url_arguments(url=data["object"]["id"], urlmap=request.app.url_map)["id"]) media = MediaEntry.query.filter_by(id=media_id).first() if media is None: return json_response( "No such 'image' with id '{0}'".format(media_id), status=404) if media.uploader != request.user.id: return json_error( "Privilege 'commenter' required to comment.", status=403) if not media.unserialize(data["object"]): return json_error( "Invalid 'image' with id '{0}'".format(media_id)) # Add location if one exists if "location" in data: Location.create(data["location"], self) media.save() activity = api_add_to_feed(request, media) return json_response(activity.serialize(request)) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error( "Unknown object type '{0}'.".format(object_type)) # Updating existing objects if data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = int( extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"]) # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403) comment = MediaComment.query.filter_by(id=obj_id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id)) # Check that the person trying to update the comment is # the author of the comment. if comment.author != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403) if not comment.unserialize(data["object"], request): return json_error("Invalid 'comment' with id '{0}'".format( obj["id"])) comment.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=comment, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(id=obj_id).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj["id"])) # Check that the person trying to update the comment is # the author of the comment. if image.uploader != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403) if not image.unserialize(obj): return json_error( "Invalid 'image' with id '{0}'".format(obj_id)) image.generate_slug() image.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=image, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "person": # check this is the same user if "id" not in obj or obj["id"] != requested_user.id: return json_error("Incorrect user id, unable to update") requested_user.unserialize(obj) requested_user.save() generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=requested_user, generator=generator) return json_response(activity.serialize(request)) elif data["verb"] == "delete": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") # Parse out the object ID obj_id = int( extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"]) if obj.get("objectType", None) == "comment": # Find the comment asked for comment = MediaComment.query.filter_by( id=obj_id, author=request.user.id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id)) # Make a delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=comment, generator=generator) # Unfortunately this has to be done while hard deletion exists context = activity.serialize(request) # now we can delete the comment comment.delete() return json_response(context) if obj.get("objectType", None) == "image": # Find the image entry = MediaEntry.query.filter_by( id=obj_id, uploader=request.user.id).first() if entry is None: return json_error( "No such 'image' with id '{0}'.".format(obj_id)) # Make the delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=entry, generator=generator) # This is because we have hard deletion context = activity.serialize(request) # Now we can delete the image entry.delete() return json_response(context) elif request.method != "GET": return json_error("Unsupported HTTP method {0}".format(request.method), status=501) feed = { "displayName": "Activities by {user}@{host}".format(user=request.user.username, host=request.host), "objectTypes": ["activity"], "url": request.base_url, "links": { "self": { "href": request.url } }, "author": request.user.serialize(request), "items": [], } # Create outbox if outbox is None: outbox = Activity.query.filter_by(actor=request.user.id) else: outbox = outbox.filter_by(actor=request.user.id) # We want the newest things at the top (issue: #1055) outbox = outbox.order_by(Activity.published.desc()) # Limit by the "count" (default: 20) limit = request.args.get("count", 20) try: limit = int(limit) except ValueError: limit = 20 # The upper most limit should be 200 limit = limit if limit < 200 else 200 # apply the limit outbox = outbox.limit(limit) # Offset (default: no offset - first <count> result) outbox = outbox.offset(request.args.get("offset", 0)) # Build feed. for activity in outbox: try: feed["items"].append(activity.serialize(request)) except AttributeError: # This occurs because of how we hard-deletion and the object # no longer existing anymore. We want to keep the Activity # in case someone wishes to look it up but we shouldn't display # it in the feed. pass feed["totalItems"] = len(feed["items"]) return json_response(feed)
def feed_endpoint(request, outbox=None): """ Handles the user's outbox - /api/user/<username>/feed """ username = request.matchdict["username"] requested_user = User.query.filter_by(username=username).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) if request.data: data = json.loads(request.data.decode()) else: data = {"verb": None, "object": {}} if request.method in ["POST", "PUT"]: # Validate that the activity is valid if "verb" not in data or "object" not in data: return json_error("Invalid activity provided.") # Check that the verb is valid if data["verb"] not in ["post", "update", "delete"]: return json_error("Verb not yet implemented", 501) # We need to check that the user they're posting to is # the person that they are. if requested_user.id != request.user.id: return json_error("Not able to post to another users feed.", status=403) # Handle new posts if data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u"commenter"): return json_error("Privilege 'commenter' required to comment.", status=403) comment = MediaComment(author=request.user.id) comment.unserialize(data["object"], request) comment.save() # Create activity for comment generator = create_generator(request) activity = create_activity( verb="post", actor=request.user, obj=comment, target=comment.get_entry, generator=generator ) return json_response(activity.serialize(request)) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = int(extract_url_arguments(url=data["object"]["id"], urlmap=request.app.url_map)["id"]) media = MediaEntry.query.filter_by(id=media_id).first() if media is None: return json_response("No such 'image' with id '{0}'".format(media_id), status=404) if media.uploader != request.user.id: return json_error("Privilege 'commenter' required to comment.", status=403) if not media.unserialize(data["object"]): return json_error("Invalid 'image' with id '{0}'".format(media_id)) # Add location if one exists if "location" in data: Location.create(data["location"], self) media.save() activity = api_add_to_feed(request, media) return json_response(activity.serialize(request)) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error("Unknown object type '{0}'.".format(object_type)) # Updating existing objects if data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = int(extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"]) # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u"commenter"): return json_error("Privilege 'commenter' required to comment.", status=403) comment = MediaComment.query.filter_by(id=obj_id).first() if comment is None: return json_error("No such 'comment' with id '{0}'.".format(obj_id)) # Check that the person trying to update the comment is # the author of the comment. if comment.author != request.user.id: return json_error("Only author of comment is able to update comment.", status=403) if not comment.unserialize(data["object"], request): return json_error("Invalid 'comment' with id '{0}'".format(obj["id"])) comment.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=comment, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(id=obj_id).first() if image is None: return json_error("No such 'image' with the id '{0}'.".format(obj["id"])) # Check that the person trying to update the comment is # the author of the comment. if image.uploader != request.user.id: return json_error("Only uploader of image is able to update image.", status=403) if not image.unserialize(obj): return json_error("Invalid 'image' with id '{0}'".format(obj_id)) image.generate_slug() image.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=image, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "person": # check this is the same user if "id" not in obj or obj["id"] != requested_user.id: return json_error("Incorrect user id, unable to update") requested_user.unserialize(obj) requested_user.save() generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=requested_user, generator=generator) return json_response(activity.serialize(request)) elif data["verb"] == "delete": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") # Parse out the object ID obj_id = int(extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"]) if obj.get("objectType", None) == "comment": # Find the comment asked for comment = MediaComment.query.filter_by(id=obj_id, author=request.user.id).first() if comment is None: return json_error("No such 'comment' with id '{0}'.".format(obj_id)) # Make a delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=comment, generator=generator) # Unfortunately this has to be done while hard deletion exists context = activity.serialize(request) # now we can delete the comment comment.delete() return json_response(context) if obj.get("objectType", None) == "image": # Find the image entry = MediaEntry.query.filter_by(id=obj_id, uploader=request.user.id).first() if entry is None: return json_error("No such 'image' with id '{0}'.".format(obj_id)) # Make the delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=entry, generator=generator) # This is because we have hard deletion context = activity.serialize(request) # Now we can delete the image entry.delete() return json_response(context) elif request.method != "GET": return json_error("Unsupported HTTP method {0}".format(request.method), status=501) feed = { "displayName": "Activities by {user}@{host}".format(user=request.user.username, host=request.host), "objectTypes": ["activity"], "url": request.base_url, "links": {"self": {"href": request.url}}, "author": request.user.serialize(request), "items": [], } # Create outbox if outbox is None: outbox = Activity.query.filter_by(actor=request.user.id) else: outbox = outbox.filter_by(actor=request.user.id) # We want the newest things at the top (issue: #1055) outbox = outbox.order_by(Activity.published.desc()) # Limit by the "count" (default: 20) limit = request.args.get("count", 20) try: limit = int(limit) except ValueError: limit = 20 # The upper most limit should be 200 limit = limit if limit < 200 else 200 # apply the limit outbox = outbox.limit(limit) # Offset (default: no offset - first <count> result) outbox = outbox.offset(request.args.get("offset", 0)) # Build feed. for activity in outbox: try: feed["items"].append(activity.serialize(request)) except AttributeError: # This occurs because of how we hard-deletion and the object # no longer existing anymore. We want to keep the Activity # in case someone wishes to look it up but we shouldn't display # it in the feed. pass feed["totalItems"] = len(feed["items"]) return json_response(feed)
def feed_endpoint(request): """ Handles the user's outbox - /api/user/<username>/feed """ username = request.matchdict["username"] requested_user = User.query.filter_by(username=username).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) if request.data: data = json.loads(request.data.decode()) else: data = {"verb": None, "object": {}} if request.method in ["POST", "PUT"]: # Validate that the activity is valid if "verb" not in data or "object" not in data: return json_error("Invalid activity provided.") # Check that the verb is valid if data["verb"] not in ["post", "update"]: return json_error("Verb not yet implemented", 501) # We need to check that the user they're posting to is # the person that they are. if requested_user.id != request.user.id: return json_error( "Not able to post to another users feed.", status=403 ) # Handle new posts if data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment(author=request.user.id) comment.unserialize(data["object"]) comment.save() data = { "verb": "post", "object": comment.serialize(request) } return json_response(data) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = int(data["object"]["id"]) media = MediaEntry.query.filter_by(id=media_id).first() if media is None: return json_response( "No such 'image' with id '{0}'".format(media_id), status=404 ) if media.uploader != request.user.id: return json_error( "Privilege 'commenter' required to comment.", status=403 ) if not media.unserialize(data["object"]): return json_error( "Invalid 'image' with id '{0}'".format(media_id) ) # Add location if one exists if "location" in data: Location.create(data["location"], self) media.save() api_add_to_feed(request, media) return json_response({ "verb": "post", "object": media.serialize(request) }) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error( "Unknown object type '{0}'.".format(object_type) ) # Updating existing objects if data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = obj["id"] # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment.query.filter_by(id=obj_id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if comment.author != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403 ) if not comment.unserialize(data["object"]): return json_error( "Invalid 'comment' with id '{0}'".format(obj_id) ) comment.save() activity = { "verb": "update", "object": comment.serialize(request), } return json_response(activity) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(id=obj_id).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if image.uploader != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403 ) if not image.unserialize(obj): return json_error( "Invalid 'image' with id '{0}'".format(obj_id) ) image.generate_slug() image.save() activity = { "verb": "update", "object": image.serialize(request), } return json_response(activity) elif obj["objectType"] == "person": # check this is the same user if "id" not in obj or obj["id"] != requested_user.id: return json_error( "Incorrect user id, unable to update" ) requested_user.unserialize(obj) requested_user.save() elif request.method != "GET": return json_error( "Unsupported HTTP method {0}".format(request.method), status=501 ) feed_url = request.urlgen( "mediagoblin.federation.feed", username=request.user.username, qualified=True ) feed = { "displayName": "Activities by {user}@{host}".format( user=request.user.username, host=request.host ), "objectTypes": ["activity"], "url": feed_url, "links": { "first": { "href": feed_url, }, "self": { "href": request.url, }, "prev": { "href": feed_url, }, "next": { "href": feed_url, } }, "author": request.user.serialize(request), "items": [], } for activity in Activity.query.filter_by(actor=request.user.id): feed["items"].append(activity.serialize(request)) feed["totalItems"] = len(feed["items"]) return json_response(feed)