-
Notifications
You must be signed in to change notification settings - Fork 535
Authorization #16
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
I have been thinking about different ways to handle authorization. I stripped out my first attempt (a set of callbacks of the resource) since I wasn't sure it was quite right. One concern I have about overriding the controller to handle authorization is that once we get patch support implemented it will be possible to have a single request create multiple operations. Authorization will need to be checked for each of these operations. I don't know how well Pundit or cancancan can be adapted to this criteria. One method I have been playing with (in my project using this gem) has been to create a custom OperationsProcessor, derived from the I would certainly appreciate any work you wish to put into this problem. I just want to make sure any solution we adopt will support multiple operations, is flexible to support different authorization packages, and is resource centric (if possible). |
lgebhardt, I'll put together an example showing the gist of what I was thinking, and see what you think. I'm thinking an interface that can be specified in the controller -- similar to how context is specified -- so nothing on the controller has to be overridden. I don't see this getting in the way for a patch single request that handles multiple operations, as whatever handles the type of patch operation that occurs can know the appropriate type of authorization that it needs to do. I currently think authorization should occur in the controller and not in the resource -- thus why I think a callback on a resource is a bad idea. |
@Samsinite, thanks, I look forward to seeing your proposal. |
From the http://jsonapi.org/format/#patch-urls
I want any authorization system we come up with to allow jsonapi-resources to support PATCH operations on the root URL. |
Authorizations are also important on the UI side for the client that will use the data. I was thinking about sending along with the object, the operation the user can perform on it. For a post the author can create, read, update, delete, but a viewer can only read. Based on that meta information, the client can change the UI to reflect it. This does not remove the need to block actions on the server based on the user's authorization, but it would provide a much more complete approach. |
It might help others to see how I'm handing authorization. I'm pretty happy with the approach. I'm using the This is what my module V1
class BaseResource < JSONAPI::Resource
[:create, :update, :remove].each do |action|
set_callback action, :before, :authorize
end
# Authorize the model for the permission required by the controller
# action. Also, mark the controller as having been policy authorized.
def authorize
controller = context.fetch(:controller)
permission = controller.action_name + "?"
controller.policy_authorized!
policy = Pundit.policy!(context.fetch(:current_user), model)
unless policy.public_send(permission)
error = Pundit::NotAuthorizedError.new("not allowed to #{permission} this #{model}")
error.query, error.record, error.policy = permission, model, policy
raise error
end
end
class << self
# Override the records method to policy scope the records
# used by JSONAPI::Resource find methods. Also, mark the controller
# as having been policy scoped.
def records(options = {})
options.fetch(:context).fetch(:controller).policy_scoped!
relation = Pundit.policy_scope!(options.fetch(:context).fetch(:current_user), _model_class)
authorize(options.fetch(:context), relation)
relation
end
private
def authorize(context, relation)
case context.fetch(:controller).action_name
when "show"
resource_for(
context.fetch(:controller).params.fetch(:controller)
).new(relation.first, context).authorize
end
end
end
end
end |
@barelyknown what are you using for authentication? My only experience with authentication in Rails so far is Devise, but that does not appear to be a good fit here. |
@wzowee Devise could work fine. I've used that or just rolled my own using |
@barelyknown I think you're on to something, I've been also thinking it'd be nice to use a pundit-like solution. Only I'm wondering if it'd be possible to have separate Policy objects with methods for each of the operations, and this object would be passed around to both resources (for GET requests / show operations) and controllers (for POST and PATCH / create and update operations). I'm still grokking the source to be able to offer a concrete solution, though |
@wzowee we're also using ember-simple-auth + rolling our own token oauth, but this setup assumes you have an ember front-end, a single trusted client. Is that your scenario? It might be different for, say, services, or a public API. |
I've evolved my approach a bit (but it's still the same basic idea). Here's some sample code. It might be helpful. module V1
class BaseResource < JSONAPI::Resource
include ResourcePolicyAuthorization
// ...
end
end module ResourcePolicyAuthorization
extend ActiveSupport::Concern
included do
[:save, :remove].each do |action|
set_callback action, :before, :authorize
end
end
delegate :authorize_policy, to: :class
def authorize
authorize_policy(context[:current_user], model, context[:controller])
end
def records_for(association_name, options={})
records = model.public_send(association_name)
return records if context.nil?
if records
authorize_policy(context[:current_user], records, context[:controller])
end
records
end
class_methods do
def records(options = {})
authorized_records(
options[:context][:current_user],
options[:records_base] || _model_class,
options[:context][:controller]
)
end
def authorized_records(user, records, controller)
records = Pundit.policy_scope!(user, records)
controller.policy_scoped!
authorize_policy(user, records, controller)
records
end
def authorize_policy(user, records, controller)
controller.policy_authorized!
case records
when ActiveRecord::Relation
return if records.count == 0
end
policy = Pundit.policy!(user, policy_record_for(records))
permission = permission_for(controller, policy)
if !_authorized?(policy, permission)
raise Pundit::NotAuthorizedError.new(
query: permission_for(controller, policy),
record: policy_record_for(records)
)
end
end
private
def policy_record_for(records)
case records
when ActiveRecord::Base then records
when ActiveRecord::Relation then records.first || records.model
end
end
def permission_for(controller, policy)
case controller.model_class == policy.model_class
when true then controller.action_name.to_s + "?"
when false then "show?"
end
end
def _authorized?(policy, permission)
policy.public_send(permission)
end
end
end |
@oliverbarnes yeah - that's pretty much my scenario at present - have gone with ember-simple-auth and devise for now (see http://johnmosesman.com/ember-simple-auth-tutorial-and-common-problems/), mainly due to time constraints and not wanting to re-implement what comes for free with devise (account locking, password reset emails etc) @barelyknown thanks for the update on how your authorisation is evolving. |
@barelyknown I'm having a hard time seeing where the authorization rules are, or would go, there |
The authorization rules are all in Pundit policies. https://github.com/elabs/pundit |
Example? |
Sure. Here's an class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def get_related_resource?
show?
end
def get_related_resources?
index?
end
def create?
false
end
def update?
false
end
def destroy?
false
end
def scope
self.class::Scope.new(user, record.class).resolve
end
def model_class
self.class.to_s[/.+(?=Policy)/].constantize
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope.none
end
end
end |
Thanks. I guess my question is how would a pundit policy handle authorization that needs to be done before hitting the model. Checking the user id requested to patch a user resource against the current user's id, for example |
It would first find the user that is the subject of the action. Then it would check if the |
Right, that's what I mean - how do we do that based only on the user id param, without actually fetching the user from the database? Maybe this is out of JR's concern in the end... there's a reason why there's separate specs for authorization (like OAuth2) and resources. Just learning this myself |
Why wouldn't you want to fetch the user with the |
Why fetch a record or even touch the database at all when you already know it’s off-limits? Best to respond with 403 as quickly as possible, IMHO On June 5, 2015 at 3:09:18 PM, Sean Devine (notifications@github.com) wrote: Why wouldn't you want to fetch the user with the user_id param? — |
How would you know? |
@barelyknown i like the approach. I'll give it a shot and provide feedback. |
The client would send it? Assuming the relationship with the owner is always present in the requests On June 7, 2015 at 7:28:12 PM, Sean Devine (notifications@github.com) wrote: How would you know? — |
That doesn't sound right (at least not for the standard case). You can authenticate who the request came from based on info in the request, but you need for the server to authorize that that user can perform whatever action they're trying to perform, and that will require looking up the some info in the DB (or wherever). |
Would it be possible to authorize before show/index with the method barelyknown uses? As far as I can tell there are no callbacks for these methods. We need to restrict access to listing for some resources and displaying certain records in others in our API. |
The approach that I use works for all actions including show/index. Are you running across a situation that doesn't seem like a fit? |
We figured it out - I was confused by the split between using callbacks for save and remove, and fetching being implemented by overriding class methods, but my colleague helped clarify it for me. |
Seems to be a bug. If I downgrade to 0.6.2 everything seems to work. Supposedly #573 fixes this issue but it doesn't seem to work for me or something else is breaking it. |
@nozpheratu The problem is probably related to this commit ace49ff. It removed some of the modules that provide the rescuable functionality. You can add them back you your controller class. For example, I added something like the following: class ApplicationController < JSONAPI::ResourceController
include ActionController::Rescue
include ActionController::Head
rescue_from Pundit::NotAuthorizedError do |exception|
head :forbidden
end
end |
I recently started a project with JSONAPI::Resources, and I'm now trying to implement authorization with Pundit. I stumbled across this and have read the entire thread multiple times over, but I have to admit I'm completely lost. I'm fairly new to Rails, and this is my first time working with JSONAPI in any capacity. Does anyone have a working demo of the techniques discussed here, or perhaps a simple explanation of what's going on for someone with a bit less Rails experience? I've tried using some of the code snippets from above, but I just get a bunch of errors for each one. Any help would be greatly appreciated. |
The code worked for me on earlier versions of the gem. From:notifications@github.heygears.comSent:April 23, 2016 3:15 PMTo:jsonapi-resources@noreply.github.heygears.comReply-to:reply@reply.github.heygears.comCc:cylehunter33@gmail.com; mention@noreply.github.heygears.comSubject:Re: [cerebris/jsonapi-resources] Authorization (#16) I recently started a project with JSONAPI::Resources, and I'm now trying to implement authorization with Pundit. I stumbled across this and have read the entire thread multiple times over, but I have to admit I'm completely lost. I'm fairly new to Rails, and this is my first time working with JSONAPI in any capacity. Does anyone have a working demo of the techniques discussed here, or perhaps a simple explanation of what's going on for someone with a bit less Rails experience? I've tried using some of the code snippets from above, but I just get a bunch of errors for each one. Any help would be greatly appreciated. —You are receiving this because you were mentioned.Reply to this email directly or view it on GitHub |
@GRardB, give our gem a try: https://github.com/venuu/jsonapi-authorization — it should help you get started. Even though it's an alpha version and most likely still contains some bugs, it should have proper authorization hooks in place for all actions to be checked. If not, then that's a bug and we'd like to address it :) |
@valscion I should document+test this better, but I have a 60-line module which hooks JR to Pundit for hummingbird.me. I'd love to compare notes and see if we can combine efforts. I know one problem with mine is that in certain cases a |
@NuckChorris that looks like a nice approach, but I'm afraid you might not get enough protection by just applying hooks to resource classes. You might need to get all the way to the operations processor level to get all the necessary information out for authorization purposes, and that's indeed what we're doing with our gem. I'm not sure your approach protects against relationship actions. We've got extensive request specs to test authorization. Check out e.g. |
Years go by. Nothing done on authorisation. Wasted a day digging into the useless gem. |
@ulitiy I can understand that you're disappointed, but there's no need to be rude. This gem works nicely for what it does, and authorization can be handled like we've done with our gem. You can't always get something for free and ready out-of-the-box, you might have to take your time to write the missing pieces yourself. |
@ulitiy Authorization can be done in many ways. If there's a particular package that you are trying to use and it's conflicting with this gem please write up an issue specific to that problem. Gems like this intentionally do not try to solve every problem and it is a decision we have made to not advocate for one particular authorization solution. |
That's actually a great stance. Maybe it could be written up somewhere, and closing this issue as it's highly unlikely this will ever be "resolved" per se? Perhaps a wiki page, or something? |
I haven't announced this gem yet (need to add tests for Rails 4.2), but https://github.com/togglepro/pundit-resources will be a drop in solution for using Pundit with jsonapi-resources for authorization. I've used the technique for a long time now on multiple projects, and it works great. |
@valscion We definitely should add a section on authorization to the README. We could list the common options and maybe document it further in the Wiki. I haven't checked out @barelyknown's https://github.com/togglepro/pundit-resources yet, but if it's "drop in" (and good, as I assume) we might just recommend that as the safe and simple solution. I'll leave this issue open until we address this in the README. |
@barelyknown that seems like a nice approach. I'd be curious to know how it compares to https://github.com/venuu/jsonapi-authorization which handles ties authorization to Pundit, too. Our gem just does it via operations processors as we thought that resource level callbacks weren't enough for everything. Operations processor also allows for more fine-grained permission handling. If your gem happens to cover the same use cases as our gem with much less complexity, that's a huge win 😄. |
@valscion I didn't even know that |
@barelyknown I'm using operations processors to protect show and index actions. eg |
Just found this thread while trying to add punding to our JR project. |
Disclaimer: I know much more about From the way I see it,
Now, how that compares to
I might still have missing details on Please correct me if I'm wrong on some aspects @barelyknown 😄 P.S. I suppose @thibaudgg has a large-ish API using @aaronbhansen might also have some insight into how these two gems could possibly compare given my points. I assume @arcreative could also tell a bit about using EDIT: |
Wow thanks for this detailed answer :) |
@valscion pundit-resources should be able to handle relationships, and they can be tested, I just haven't implemented the tests yet. The process for creating this gem was taking some code that we knew to work and extracting it to a gem, meaning we needed new tests, some of which just haven't been implemented yet (but I'm on it). |
Thanks, @alyssais! I'll edit my comment accordingly :) |
No problem @valscion. Everything else you've written about pundit-resources looks good. 👍 |
@valscion I'm not sure if you have seen the recent changes we have made to JR regarding More details on the new Processors can be found in the Operation Processors section of the README. I just became aware of |
We've seen them 😉 venuu/jsonapi-authorization#22. We did anticipate that there might be changes like this, as we were fiddling with some private-ish APIs. I hope we can somehow make the approach backwards-compatible in
No worries, the new approach to processors makes a lot of sense.
I haven't yet figured how much trouble we're in if we want to retain backwards compatibility with JR |
By the way, was this issue now resolved when #769 was merged? 🎉
|
Hey guys,
This gem/library looks great! What about directly supporting an option to easily hook up authorization instead of requiring the resource controller index/show/create/update/destroy methods to be overridden?
Something like a simple interface that filters for index/read_list, returns true/false for show/create/update/destroy, and if in the future updating lists or destroying lists are supported, the interface could be updated to support filtering those as well. Than it would be straight forward for whoever to override it to support their preferred method of authorization, such as pundit or cancancan. I wouldn't mind implementing this if you guys are interested.
The text was updated successfully, but these errors were encountered: