Okay, something is seriously broken here...
I am using Active Model Serializer and Pundit for my Rails 5 JSONAPI server and Ember for my frontend application.
I have User
model and Pundit policy for User model which prevent non-authors from viewing unpublished stories and chapters.
At the moment, I am seeing a weird problem which goes like this:
1. UserA creates StoryA, and two published chapters Chapter1 and Chapter2
2. UserA then creates two unpublished chapters Chapter3 and Chapter4
3. UserA logouts
4. UserB logins
5. UserB views the same story created by UserA
6. Server policy kicks in and scope the results to only published chapters since UserB isn't the author.
7. * An SQL DELETE query is sent to delete the two unpublished stories for some odd reason.
Here's some screenshot:
(As you can see, UserB only sees the two published chapters which is correct but...)
Looking at the Rails console, I see the DELETE query during a ChaptersController#show
CRUD action:
Started GET "/stories/16" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Processing by StoriesController#show as JSON
Parameters: {"id"=>"16"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Story Load (0.1ms) SELECT "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Chapter Load (0.3ms) SELECT "chapters".* FROM "chapters" INNER JOIN "stories" ON "stories"."id" = "chapters"."story_id" INNER JOIN "users" ON "users"."id" = "stories"."user_id" WHERE "chapters"."story_id" = ? AND ((stories.published = 't' AND chapters.published = 't') OR stories.user_id = 2) [["story_id", 16]]
Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."story_id" = ? [["story_id", 16]]
(0.1ms) begin transaction
Started GET "/chapters/26" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
SQL (0.3ms) DELETE FROM "chapters" WHERE "chapters"."id" = ? [["id", 32]]
Started GET "/chapters/27" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Processing by ChaptersController#show as JSON
SQL (0.1ms) DELETE FROM "chapters" WHERE "chapters"."id" = ? [["id", 33]]
Processing by ChaptersController#show as JSON
Parameters: {"id"=>"26"}
(2.1ms) commit transaction
Parameters: {"id"=>"27"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
[active_model_serializers] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Chapter Load (0.1ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 26], ["LIMIT", 1]]
[active_model_serializers] Story Load (0.4ms) SELECT "stories".* FROM "stories" WHERE "stories"."user_id" = ? [["user_id", 1]]
Chapter Load (0.2ms) SELECT "chapters".* FROM "chapters" WHERE "chapters"."id" = ? LIMIT ? [["id", 27], ["LIMIT", 1]]
Started GET "/chapters/32" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Started GET "/chapters/33" for 127.0.0.1 at 2017-11-05 17:02:53 +0800
Story Load (0.2ms) SELECT "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
[active_model_serializers] Rendered StorySerializer with ActiveModelSerializers::Adapter::JsonApi (22.64ms)
Story Load (0.1ms) SELECT "stories".* FROM "stories" WHERE "stories"."id" = ? LIMIT ? [["id", 16], ["LIMIT", 1]]
Processing by ChaptersController#show as JSON
Processing by ChaptersController#show as JSON
[active_model_serializers] Rendered ChapterSerializer with ActiveModelSerializers::Adapter::JsonApi (0.82ms)
Completed 200 OK in 43ms (Views: 27.1ms | ActiveRecord: 3.9ms)
My ChaptersController show action does not even have the word "delete" or "destroy" in it...so how does the record get deleted?
# CHAPTERS CONTROLLER
def show
chapter = Chapter.find_by(id: params[:id])
if chapter.present?
authorize chapter
render json: chapter, status: :ok
else
skip_authorization
render json: { error: "Chapter not found" }, status: :not_found
end
end
My Chapter Policy show method:
# CHAPTER PUNDIT POLICY
def show?
(@record.published? && @record.story.published?) || (@record.story.user == @user)
end
My StoriesController Show action looks like:
# STORIES CONTROLLER
def show
story = Story.find_by(id: params[:id])
if story.present?
authorize story
story.chapters = policy_scope(story.chapters)
render json: story, include: [:user, :chapters], status: :ok
else
skip_authorization
render json: { errors: "Story not found" }, status: :not_found
end
end
I thought it might be Ember doing some funny extra query behind the scenes but I use Postman Mac application to test viewing the story and sure enough, the unpublished chapters are deleted without going through Ember at all. It's happening server side for some odd reason =/
Any ideas?
It looks to me like this is actually being done in StoriesController#show. You should try again testing only this method (in RSpec or something), and you can also step through the code to pinpoint exactly where the delete is occurring. My guess is:
story.chapters = policy_scope(story.chapters)
You're changing the story chapters here. Presumably you want to do something like
@displayed_chapters = policy_scope(story.chapters)