Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add authorization features and a Pundit mixin #971

Merged
merged 3 commits into from
Dec 4, 2017

Conversation

pedantic-git
Copy link
Contributor

This PR adds basic authorization features to Administrate, allowing different admins access to different data/actions.

Users are free to roll their own authorization, or use any authz gems they like, but since I personally think Pundit is the best of the bunch, I've implemented a controller concern that allows users to get Pundit support in one line.

Pundit isn't added as a dependency, except for the tests.

I've added an authorization.md file to the docs/ with full instructions... let me know what you think and if you'd like any changes!

def destroy?
false
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def update?
record.address_state == "AZ"
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def create?
false
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

scope.where(customer: user)
end
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def resolve
scope.all
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

end
it 'does not show edit actions for records elsewhere' do
o = create :order, customer: user, address_state: 'GA'
expect(controller.show_action? :edit, o).to be false

Choose a reason for hiding this comment

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

Add parentheses to nested method call controller.show_action? :edit, o.

expect(controller.show_action? :edit, o).to be true
end
it 'does not show edit actions for records elsewhere' do
o = create :order, customer: user, address_state: 'GA'

Choose a reason for hiding this comment

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

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

o = create :order, customer: user, address_state: 'AZ'
expect(controller.show_action? :edit, o).to be true
end
it 'does not show edit actions for records elsewhere' do

Choose a reason for hiding this comment

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

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

describe '#show_action?' do
it 'shows edit actions for records in AZ' do
o = create :order, customer: user, address_state: 'AZ'
expect(controller.show_action? :edit, o).to be true

Choose a reason for hiding this comment

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

Add parentheses to nested method call controller.show_action? :edit, o.

end
describe '#show_action?' do
it 'shows edit actions for records in AZ' do
o = create :order, customer: user, address_state: 'AZ'

Choose a reason for hiding this comment

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

Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

Copy link
Collaborator

@pablobm pablobm left a comment

Choose a reason for hiding this comment

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

This looks pretty good to me. I like the feature and the agnostic approach.

I would say:

  1. Please address Hound's warnings. I'm surprised there aren't more, I wonder if it failed?
  2. Remove the comments, as I don't think they are necessary and there aren't similar ones (I think!) in the codebase.

@@ -104,6 +107,8 @@ def dashboard

def requested_resource
@_requested_resource ||= find_resource(params[:id])
authorize_resource(@_requested_resource)
@_requested_resource
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

This breaks the existing memoization. Here's an alternative, saving us from (potentially) checking the authorization multiple times:

def requested_resource
  @_requested_resource ||= find_resource(params[:id]).tap do |r|
    authorize_resource(r)
  end
end

But then I think... we could go further. If the spec for authorize_resource required it to return the resource on success, we could do this:

def requested_resource
  @_requested_resource ||= authorize_resource(find_resource(params[:id]))
end

In fact that's what Pundit's own authorize does, which in turn means that the implementation provided by Punditize does this, although not the default, blank implementation.

But I'm probably complicating this too much for too little gain, so I'll be happy with something along the lines of my first alternative.

locals = capture_view_locals { get :index }
expect(locals[:resources].count).to eq(9) # only my orders
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please separate these with blank lines. I'm surprised that Hound is not complaining about this and the single quotes?

@nickcharlton
Copy link
Member

Thanks @pedantic-git! I know @pablobm left some comments, but I'm thinking it'd be good to get this into 0.9, which should be in a few weeks time (i.e.: the next release).

@pedantic-git
Copy link
Contributor Author

Sure! I'll try to get onto these tomorrow. Just working on another Administrate PR right now (trying to get editing working for Field::Polymorphic... nearly there too!)

def destroy?
false
end

Choose a reason for hiding this comment

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

Extra empty line detected at class body end.

@@ -0,0 +1,25 @@
class OrderPolicy < ApplicationPolicy

Choose a reason for hiding this comment

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

Extra empty line detected at class body beginning.

expect(controller.show_action?(:destroy, o)).to be false
end
end

Choose a reason for hiding this comment

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

Extra empty line detected at block body end.

describe "DELETE destroy" do
it "never allows me to delete a record" do
o = create :order, customer: user, address_state: "AZ"
expect{delete :destroy, id: o.id}.to raise_error(Pundit::NotAuthorizedError)

Choose a reason for hiding this comment

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

Space missing to the left of {.
Space missing inside {.
Space missing inside }.
Line is too long. [84/80]


it "does not allow me to edit other records" do
ga = create :order, customer: user, address_state: "GA"
expect{get :edit, id: ga.id}.to raise_error(Pundit::NotAuthorizedError)

Choose a reason for hiding this comment

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

Space missing to the left of {.
Space missing inside {.
Space missing inside }.

end

end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def show_action?(action, resource)
Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym)
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def authorize_resource(resource)
authorize resource
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def scoped_resource
policy_scope_admin super
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

module Punditize
extend ActiveSupport::Concern
include Pundit

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def destroy?
false
end

Choose a reason for hiding this comment

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

Extra empty line detected at class body end.

@@ -0,0 +1,25 @@
class OrderPolicy < ApplicationPolicy

Choose a reason for hiding this comment

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

Extra empty line detected at class body beginning.

expect(controller.show_action?(:destroy, o)).to be false
end
end

Choose a reason for hiding this comment

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

Extra empty line detected at block body end.

describe "DELETE destroy" do
it "never allows me to delete a record" do
o = create :order, customer: user, address_state: "AZ"
expect{delete :destroy, id: o.id}.to raise_error(Pundit::NotAuthorizedError)

Choose a reason for hiding this comment

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

Space missing to the left of {.
Space missing inside {.
Space missing inside }.
Line is too long. [84/80]


it "does not allow me to edit other records" do
ga = create :order, customer: user, address_state: "GA"
expect{get :edit, id: ga.id}.to raise_error(Pundit::NotAuthorizedError)

Choose a reason for hiding this comment

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

Space missing to the left of {.
Space missing inside {.
Space missing inside }.

end

end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def show_action?(action, resource)
Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym)
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def authorize_resource(resource)
authorize resource
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def scoped_resource
policy_scope_admin super
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

module Punditize
extend ActiveSupport::Concern
include Pundit

Choose a reason for hiding this comment

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

Trailing whitespace detected.

def show_action?(action, resource)
Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym)
end

Choose a reason for hiding this comment

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

Extra empty line detected at block body end.

def authorize_resource(resource)
resource
end

Choose a reason for hiding this comment

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

Extra empty line detected at class body end.

@@ -145,5 +148,20 @@ def show_search_bar?
dashboard.collection_attributes
).any? { |_name, attribute| attribute.searchable? }
end

def show_action?(action, resource)

Choose a reason for hiding this comment

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

Unused method argument - action. If it's necessary, use _ or _action as an argument name to indicate that it won't be used. You can also write as show_action?() if you want the method to accept any arguments but don't care about them.
Unused method argument - resource. If it's necessary, use _ or _resource as an argument name to indicate that it won't be used. You can also write as show_action?(
) if you want the method to accept any arguments but don't care about them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not addressing this - it's a method signature that is meant to be overridden in subclasses so the arguments and their names are important.

@@ -71,7 +74,7 @@ def destroy
flash[:notice] = translate_with_resource("destroy.success")
redirect_to action: :index
end

Choose a reason for hiding this comment

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

Trailing whitespace detected.

@pedantic-git pedantic-git force-pushed the pundit branch 3 times, most recently from 86e29a6 to 5b12481 Compare September 28, 2017 09:56
@pedantic-git
Copy link
Contributor Author

OK! I've addressed all but two of the Hound violations (see above for my reasoning), I've changed the way authorize_resource works (thanks for spotting this, @pablobm!) and I've removed the comments.

[Removing comments is still something I struggle with - I can understand other elements of house style that I disagree with but this one always seems very counterintuitive to me.]

Let me know if I can do anything else!

@pedantic-git
Copy link
Contributor Author

Just realized that Pundit 1.1.0 (the latest gem) doesn't return the object from authorize - it returns true. Shocking there hasn't been a gem release in over a year, but in the meantime I'll change the requested_resource to use tap instead.

@@ -27,7 +27,7 @@ It displays a header, and renders the `_form` partial to do the heavy lifting.
"#{t("administrate.actions.show")} #{page.page_title}",
[namespace, page.resource],
class: "button",
) if valid_action? :show %>
) if valid_action?(:show) && show_action?(:show, page.resource) %>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use only show_action? in all cases?
Like

def show_action?(action, resource)
  valid_action?(action, resource) && authorized?(action, resource)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @pustomytnyk - #show_action? is a method that users of administrate can (and should) override in their controllers. If we implement it in terms of #valid_action? in the base controller and again in the Pundit mixin, there's still opportunity for administrate users to accidentally break the expected behaviour by overriding it to something that's not.

@nickcharlton
Copy link
Member

Thanks, @pedantic-git! I'm (finally!) going to merge this now.

@nickcharlton nickcharlton merged commit 055ef76 into thoughtbot:master Dec 4, 2017
@pedantic-git
Copy link
Contributor Author

Yay! Thanks @nickcharlton !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants