UPDATE on 2016-10-29:
The approach I want to take on authorizing relationships relies on always using a whitelist instead of blacklist. This means that:
- There won't be a generic
allow_relationship? method that would handle all relationships.
- Using
update? after trying to set a foreign key won't work, as it is a blacklist approach.
So even at the cost of some policies becoming huge, I want to keep each method as small and unambiguous as possible. That means methods named like this:
class CommentPolicy
def allow_relationship_article?(article)
# ...
end
end
I haven't yet made up my mind on which side of the association the authorization should happen, but I'd like that choice to be abstracted away from the AuthorizingProcessor. It could live inside DefaultPunditAuthorizer, if it would make sense, or we could come up with a new place for that logic to live in.
See my comment about this for more details, and to continue the discussion.
Open this to read the old proposal that kicked off the conversation
For a create request that looks like this:
{
"data": {
"id": "post-1",
"type": "posts",
"relationships": {
"author": {
"data": {
"id": "user-1", "type": "user"
}
},
"comments": {
"data": [
{ "id": "comment-1", "type": "comments" },
{ "id": "comment-2", "type": "comments" }
]
},
"tags": {
"data": [
{ "id": "tag-1", "type": "tags" },
{ "id": "tag-2", "type": "tags" }
]
}
}
}
}
We authorize with these methods:
PostPolicy.new(post_1, current_user).create?
PostPolicy.new(post_1, current_user).associate_with_author(user_1)?
PostPolicy.new(post_1, current_user).associate_with_comments([comment_1, comment_2])?
PostPolicy.new(post_1, current_user).associate_with_tags([tag_1, tag_2])?
For this request, no matter if it's an update or a create call, we are able to only use PostPolicy to authorize for every association.
I can't see there being any other way as we _cannot_ change the UserPolicy#associate_with_comments call to these without a huge scope creep:
CommentPolicy.new(comment_1, user).associate_with_post(post_1)?
CommentPolicy.new(comment_2, user).associate_with_post(post_1)?
Who knows, the Comment resource might even call that association with a completely different name than post underneath!
If that request would be an update request, it would only change the PostPolicy#create? call to be PostPolicy#update? and the association checks would remain the same.
I know this goes against Pundit architecture, but I can't really see there being any other way because we aren't able to call post_1.author = author_1 nor post_1.comments = [comment_1, comment_2] before authorizing as it would implicitly save the association immediately. So we aren't able to just use the post_1.author value in PostPolicy#update? to check for authorization, as it isn't set yet.
UPDATE on 2016-10-29:
The approach I want to take on authorizing relationships relies on always using a whitelist instead of blacklist. This means that:
allow_relationship?method that would handle all relationships.update?after trying to set a foreign key won't work, as it is a blacklist approach.So even at the cost of some policies becoming huge, I want to keep each method as small and unambiguous as possible. That means methods named like this:
I haven't yet made up my mind on which side of the association the authorization should happen, but I'd like that choice to be abstracted away from the
AuthorizingProcessor. It could live insideDefaultPunditAuthorizer, if it would make sense, or we could come up with a new place for that logic to live in.See my comment about this for more details, and to continue the discussion.
Open this to read the old proposal that kicked off the conversation
For a create request that looks like this:
{ "data": { "id": "post-1", "type": "posts", "relationships": { "author": { "data": { "id": "user-1", "type": "user" } }, "comments": { "data": [ { "id": "comment-1", "type": "comments" }, { "id": "comment-2", "type": "comments" } ] }, "tags": { "data": [ { "id": "tag-1", "type": "tags" }, { "id": "tag-2", "type": "tags" } ] } } } }We authorize with these methods:
PostPolicy.new(post_1, current_user).create?PostPolicy.new(post_1, current_user).associate_with_author(user_1)?PostPolicy.new(post_1, current_user).associate_with_comments([comment_1, comment_2])?PostPolicy.new(post_1, current_user).associate_with_tags([tag_1, tag_2])?For this request, no matter if it's an update or a create call, we are able to only use
PostPolicyto authorize for every association.I can't see there being any other way as we _cannot_ change the
UserPolicy#associate_with_commentscall to these without a huge scope creep:CommentPolicy.new(comment_1, user).associate_with_post(post_1)?CommentPolicy.new(comment_2, user).associate_with_post(post_1)?Who knows, the
Commentresource might even call that association with a completely different name thanpostunderneath!If that request would be an update request, it would only change the
PostPolicy#create?call to bePostPolicy#update?and the association checks would remain the same.I know this goes against Pundit architecture, but I can't really see there being any other way because we aren't able to call
post_1.author = author_1norpost_1.comments = [comment_1, comment_2]before authorizing as it would implicitly save the association immediately. So we aren't able to just use thepost_1.authorvalue inPostPolicy#update?to check for authorization, as it isn't set yet.has_oneassociation reference — When are objects saved? (Ruby on Rails Guides)has_manyassociation reference — When are objects saved? (Ruby on Rails Guides)