Skip to content

Check for authorization against related records#119

Merged
valscion merged 9 commits intovenuu:masterfrom
brianswko:check-related-on-update
Mar 28, 2019
Merged

Check for authorization against related records#119
valscion merged 9 commits intovenuu:masterfrom
brianswko:check-related-on-update

Conversation

@brianswko
Copy link
Copy Markdown
Contributor

@brianswko brianswko commented Mar 21, 2019

Fixes #118

This PR fixes a bug where related resources are not checked for authorization if they are not in the user's scope.

Also fixes inconsistencies between the behavior of has-one, has-many, and polymorphic relationships to always return a 404 instead of sometimes returning a 403 when the user should not be able to see that a given record exists.

@valscion
Copy link
Copy Markdown
Member

Sorry about Travis CI failing ...again... we've fixed the errors caused by bundler upgrade several times now but it seems they keep on failing.

@valscion
Copy link
Copy Markdown
Member

I fixed the CI in #120 and merged master here. Let's see what the CI says now.

@valscion
Copy link
Copy Markdown
Member

valscion commented Mar 21, 2019

CI seems to be happy. And so am I ☺️

This seems like it's ready to go, right? How would we describe this change, similarly to how we've done change docs in the past? https://github.com/venuu/jsonapi-authorization/releases

@valscion
Copy link
Copy Markdown
Member

@all-contributors please add @brianswko for bug, test and code

Let me know if you'd rather not be listed in the README. I'll wait for your confirmaton before merging the README.md update by the bot.

@allcontributors
Copy link
Copy Markdown
Contributor

@valscion

I've put up a pull request to add @brianswko! 🎉

context 'which is out of scope' do
let(:policy_scope) { Article.none }

it { is_expected.to be_forbidden }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valscion I just realized while testing that instead of being forbidden this should probably be a not found response. I'm going to rework the processor a bit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super ☺️. Take your time

@brianswko
Copy link
Copy Markdown
Contributor Author

Proposed description:

Fixes PATCH and POST requests to check if the user has the correct permissions for every given object in a has-many relationship

For example:
If a user does not have access to (meaning the pundit scope does not include) the author with ID 2
i.e. AuthorPolicy::Scope.new(user, Author).resolve.include?(Author.find(2)) # => false
And the following request is called:

PATCH /books/1

"data": {
  "type": "books",
  "id": "1",
  "attributes": {...},
  "relationships": {
    "authors": {
      "data": [
        { "type": "authors", "id": "1" },
        { "type": "authors", "id": "2" }
      ]
    }
  }
}

Previously: Would return a 20x and update the book to include author 2
Now: Will return a 404 and not update the book since the user is unable to find author 2

In some scenarios, this will cause a 404 to be returned where a 403 used to be returned.

@brianswko
Copy link
Copy Markdown
Contributor Author

@valscion I've simplified the change in the processor, but ended up having to make a decent amount of changes to the specs. Hoping those all make sense to you.

I'm conflicted as to whether this should be a major or minor version change, as there will almost certainly be a not-insignificant number of projects that will start seeing specs fail as bugs in specs are exposed and formerly incorrect 403s start returning 404s. Will leave that decision in your hands.

end
allow_any_instance_of(UserPolicy::Scope).to receive(:resolve) do |policy_scope|
user_policy_scope.merge(policy_scope.scope.all)
end
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a bit of a hard time figuring out what these policy scope changes do. It seems like it's overriding the policy scope stubbing to always return all results?

Maybe there could be some sort of a documentation comment here to tell why this is necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to get rid of these entirely, but that required a much larger overhaul of the tests than I wanted to take on.

As previously written, this spec never checked which has_many records were returned except in situations where the spec stubbed the comments_policy_scope to be Comment.limit(1), and it masked a fundamental problem with the mock when I added additional scenarios to test.

def records_for(association_name)
record_or_records = @model.public_send(association_name)
relationship = fetch_relationship(association_name)
case relationship
when JSONAPI::Relationship::ToOne
record_or_records
when JSONAPI::Relationship::ToMany
user_context = JSONAPI::Authorization.configuration.user_context(context)
::Pundit.policy_scope!(user_context, record_or_records)
else
raise "Unknown relationship type #{relationship.inspect}"
end
end

The value of policy_scope! is supposed to be the records that the user has access to, further restricted by the specific record_or_records in the association. A simple and_return like what was here before would return all records in the user's scope, even if it had nothing to do with the @model under test. For example, a user who has access to all articles and looked up the articles written by a single user would get a list of all articles instead of just the articles written by that user.

merge acts like a sql AND, so this change allows the stubbed scope to return something more similar to how it should actually behave outside of tests.

Will add a comment for this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks for clarifying!

expect(json_included.length).to eq(1)
expect(json_included.first["id"]).to eq(comments_policy_scope.first.id.to_s)
it 'includes only comments allowed by policy scope and associated with the article' do
expect(json_included.length).to eq(article.comments.count)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so does this now test that there are more records than were allowed? I.e. do we lose test coverage on the pundit policy scope being applied?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the specs specifically testing for limited scopes to https://github.com/venuu/jsonapi-authorization/pull/119/files#diff-f3a557fdd906ce389d7b5d190c721b99R220. I had to move them out of this shared spec since the behavior is different for GET (which returns the relationships but limited by the scope) vs POST (which should return a 404).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks!

@valscion
Copy link
Copy Markdown
Member

valscion commented Mar 26, 2019

#119 (comment)

Fixes PATCH and POST requests to check if the user has the correct permissions for every given object in a has-many relationship

Nice. This entire change description is great.

I'm conflicted as to whether this should be a major or minor version change, as there will almost certainly be a not-insignificant number of projects that will start seeing specs fail as bugs in specs are exposed and formerly incorrect 403s start returning 404s. Will leave that decision in your hands.

Given we only recently upgraded to 2.0.0 to fix an authorization bug, and only less than 1000 downloads as of this date have been done against v2.0.0, we could do this change and just bump to 3.0.0.

I'd rather signal a potentially backwards incompatible change with more major versions than to squint my eyes and think this could be a backwards-compatible change.

So once this is ready to be merged, let's punt this to 3.0.0.

@valscion
Copy link
Copy Markdown
Member

This looks like it's ready to go for me! What do you think? Is it ready?

I'll update the README to clarify what's the intent on version numbering here: #123. I think it's likely to be helpful given how this change will be shipped as v3.0.0 once we merge it.

I'll copy the text of #119 (comment) for the release notes. Let me know if there's any changes you'd like there to be.

@brianswko
Copy link
Copy Markdown
Contributor Author

Yes it's ready to go, and that comment works for the changelog.

@valscion valscion merged commit 0708aaf into venuu:master Mar 28, 2019
@valscion
Copy link
Copy Markdown
Member

valscion commented Mar 28, 2019

Thank you! This has now been released as v3.0.0 🎉

brianswko added a commit to brianswko/jsonapi-authorization that referenced this pull request Oct 2, 2019
* AuthorizingProcessor#related_models_with_context should not use the scope

* Remove unused AuthorizingProcessor#related_models

* Check all related records in authorize_remove_to_many_relationships

* Return 404 instead of 403 for records out of scope

* Style fixes

* Optimization for Array#size

* Comment to clarify changes to mocks

* Better array matching in specs
brianswko added a commit to brianswko/jsonapi-authorization that referenced this pull request Oct 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Updating related resources does not check against the related records

2 participants