From 9c602458d830c1ebcd0ede7c14f7ddef79e2a73f Mon Sep 17 00:00:00 2001 From: Jessica Tallon Date: Fri, 21 Nov 2014 13:18:25 +0000 Subject: [PATCH] Fix #1025 - Make API IDs IRIs --- mediagoblin/db/mixin.py | 8 ++++++- mediagoblin/db/models.py | 39 +++++++++++++++++++++++---------- mediagoblin/federation/views.py | 18 ++++++++++----- mediagoblin/tests/test_api.py | 19 +++++++++------- mediagoblin/tools/routing.py | 15 +++++++++++++ 5 files changed, 74 insertions(+), 25 deletions(-) diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index 7b1b1195..9b5c0e8e 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -448,10 +448,16 @@ class ActivityMixin(object): return self.content def serialize(self, request): + href = request.urlgen( + "mediagoblin.federation.object", + object_type=self.object_type, + id=self.id, + qualified=True + ) published = UTC.localize(self.published) updated = UTC.localize(self.updated) obj = { - "id": self.id, + "id": href, "actor": self.get_actor.serialize(request), "verb": self.verb, "published": published.isoformat(), diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 2de319d6..12757eda 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -40,6 +40,7 @@ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \ ActivityMixin from mediagoblin.tools.files import delete_media_files from mediagoblin.tools.common import import_component +from mediagoblin.tools.routing import extract_url_arguments import six from pytz import UTC @@ -526,11 +527,17 @@ class MediaEntry(Base, MediaEntryMixin): def serialize(self, request, show_comments=True): """ Unserialize MediaEntry to object """ + href = request.urlgen( + "mediagoblin.federation.object", + object_type=self.object_type, + id=self.id, + qualified=True + ) author = self.get_uploader published = UTC.localize(self.created) updated = UTC.localize(self.created) context = { - "id": self.id, + "id": href, "author": author.serialize(request), "objectType": self.object_type, "url": self.url_for_self(request.urlgen, qualified=True), @@ -547,12 +554,7 @@ class MediaEntry(Base, MediaEntryMixin): }, "links": { "self": { - "href": request.urlgen( - "mediagoblin.federation.object", - object_type=self.object_type, - id=self.id, - qualified=True - ), + "href": href, }, } @@ -755,10 +757,16 @@ class MediaComment(Base, MediaCommentMixin): def serialize(self, request): """ Unserialize to python dictionary for API """ + href = request.urlgen( + "mediagoblin.federation.object", + object_type=self.object_type, + id=self.id, + qualified=True + ) media = MediaEntry.query.filter_by(id=self.media_entry).first() author = self.get_author context = { - "id": self.id, + "id": href, "objectType": self.object_type, "content": self.content, "inReplyTo": media.serialize(request, show_comments=False), @@ -770,7 +778,7 @@ class MediaComment(Base, MediaCommentMixin): return context - def unserialize(self, data): + def unserialize(self, data, request): """ Takes API objects and unserializes on existing comment """ # Do initial checks to verify the object is correct required_attributes = ["content", "inReplyTo"] @@ -784,7 +792,10 @@ class MediaComment(Base, MediaCommentMixin): # Validate that the ID is correct try: - media_id = int(data["inReplyTo"]["id"]) + media_id = int(extract_url_arguments( + url=data["inReplyTo"]["id"], + urlmap=request.app.url_map + )["id"]) except ValueError: return False @@ -1214,10 +1225,16 @@ class Generator(Base): ) def serialize(self, request): + href = request.urlgen( + "mediagoblin.federation.object", + object_type=self.object_type, + id=self.id, + qualified=True + ) published = UTC.localize(self.published) updated = UTC.localize(self.updated) return { - "id": self.id, + "id": href, "displayName": self.name, "published": published.isoformat(), "updated": updated.isoformat(), diff --git a/mediagoblin/federation/views.py b/mediagoblin/federation/views.py index 715cb8cd..13f93985 100644 --- a/mediagoblin/federation/views.py +++ b/mediagoblin/federation/views.py @@ -1,4 +1,4 @@ -# GNU MediaGoblin -- federated, autonomous media hosting +# GN MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ from werkzeug.datastructures import FileStorage from mediagoblin.decorators import oauth_required, require_active_login from mediagoblin.federation.decorators import user_has_privilege from mediagoblin.db.models import User, MediaEntry, MediaComment, Activity +from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tools.response import redirect, json_response, json_error, \ render_404, render_to_response from mediagoblin.meddleware.csrf import csrf_exempt @@ -177,7 +178,7 @@ def feed_endpoint(request): ) comment = MediaComment(author=request.user.id) - comment.unserialize(data["object"]) + comment.unserialize(data["object"], request) comment.save() data = { "verb": "post", @@ -187,7 +188,11 @@ def feed_endpoint(request): elif obj.get("objectType", None) == "image": # Posting an image to the feed - media_id = int(data["object"]["id"]) + 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: @@ -245,7 +250,10 @@ def feed_endpoint(request): if "id" not in obj: return json_error("Object ID has not been specified.") - obj_id = obj["id"] + obj_id = int(extract_url_arguments( + url=obj["id"], + urlmap=request.app.url_map + )["id"]) # Now try and find object if obj["objectType"] == "comment": @@ -374,7 +382,7 @@ def object_endpoint(request): """ Lookup for a object type """ object_type = request.matchdict["object_type"] try: - object_id = int(request.matchdict["id"]) + object_id = request.matchdict["id"] except ValueError: error = "Invalid object ID '{0}' for '{1}'".format( request.matchdict["id"], diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py index 6b0722aa..698b1f0d 100644 --- a/mediagoblin/tests/test_api.py +++ b/mediagoblin/tests/test_api.py @@ -26,6 +26,7 @@ from webtest import AppError from .resources import GOOD_JPG from mediagoblin import mg_globals from mediagoblin.db.models import User, MediaEntry, MediaComment +from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tests.tools import fixture_add_user from mediagoblin.moderation.tools import take_away_privileges @@ -187,7 +188,8 @@ class TestAPI(object): # Lets change the image uploader to be self.other_user, this is easier # than uploading the image as someone else as the way self.mocked_oauth_required # and self._upload_image. - media = MediaEntry.query.filter_by(id=data["object"]["id"]).first() + id = int(data["object"]["id"].split("/")[-1]) + media = MediaEntry.query.filter_by(id=id).first() media.uploader = self.other_user.id media.save() @@ -230,13 +232,14 @@ class TestAPI(object): image = json.loads(response.body.decode())["object"] # Check everything has been set on the media correctly - media = MediaEntry.query.filter_by(id=image["id"]).first() + id = int(image["id"].split("/")[-1]) + media = MediaEntry.query.filter_by(id=id).first() assert media.title == title assert media.description == description assert media.license == license # Check we're being given back everything we should on an update - assert image["id"] == media.id + assert int(image["id"].split("/")[-1]) == media.id assert image["displayName"] == title assert image["content"] == description assert image["license"] == license @@ -285,10 +288,10 @@ class TestAPI(object): request = test_app.get(object_uri) image = json.loads(request.body.decode()) - entry = MediaEntry.query.filter_by(id=image["id"]).first() + entry_id = int(image["id"].split("/")[-1]) + entry = MediaEntry.query.filter_by(id=entry_id).first() assert request.status_code == 200 - assert entry.id == image["id"] assert "image" in image assert "fullImage" in image @@ -316,7 +319,8 @@ class TestAPI(object): assert response.status_code == 200 # Find the objects in the database - media = MediaEntry.query.filter_by(id=data["object"]["id"]).first() + media_id = int(data["object"]["id"].split("/")[-1]) + media = MediaEntry.query.filter_by(id=media_id).first() comment = media.get_comments()[0] # Tests that it matches in the database @@ -324,7 +328,6 @@ class TestAPI(object): assert comment.content == content # Test that the response is what we should be given - assert comment.id == comment_data["object"]["id"] assert comment.content == comment_data["object"]["content"] def test_unable_to_post_comment_as_someone_else(self, test_app): @@ -379,7 +382,7 @@ class TestAPI(object): response, comment_data = self._activity_to_feed(test_app, activity) # change who uploaded the comment as it's easier than changing - comment_id = comment_data["object"]["id"] + comment_id = int(comment_data["object"]["id"].split("/")[-1]) comment = MediaComment.query.filter_by(id=comment_id).first() comment.author = self.other_user.id comment.save() diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py index a15795fe..2ff003b7 100644 --- a/mediagoblin/tools/routing.py +++ b/mediagoblin/tools/routing.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import logging +import urlparse import six from werkzeug.routing import Map, Rule @@ -65,3 +66,17 @@ def mount(mountpoint, routes): for endpoint, url, controller in routes: url = "%s/%s" % (mountpoint.rstrip('/'), url.lstrip('/')) add_route(endpoint, url, controller) + +def extract_url_arguments(url, urlmap): + """ + This extracts the URL arguments from a given URL + """ + parsed_url = urlparse.urlparse(url) + map_adapter = urlmap.bind( + server_name=parsed_url.netloc, + script_name=parsed_url.path, + url_scheme=parsed_url.scheme, + path_info=parsed_url.path + ) + + return map_adapter.match()[1] -- 2.25.1