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 allow_with_resource #8

Merged
merged 13 commits into from
Dec 9, 2016
Merged

add allow_with_resource #8

merged 13 commits into from
Dec 9, 2016

Conversation

jnwaletzko
Copy link
Contributor

@jnwaletzko jnwaletzko commented Dec 5, 2016

Description and Impact

Adds an allow_with_resource method that will require a resource check when accessing the endpoint. This will act exactly the same as allow except that the CustomRoleObject used will need a method that checks if the resource matches the role. A method role_resource will need to be defined on the controller to allow the passing of the resource object.
Ex.

class SomeCustomRoleObject
  def resource?(resource)
    self.resources.includes?(resource)
  end
end

class ProfilesController < ApplicationController
  allow_with_resource(:admin).to_access(:index)
 
  ...

  private def role_resource
     { resource: params[:resource_id] }
  end
end

Deploy Plan

Does Platform Operations need to know anything special about this deploy? Are migrations present?

Rollback Plan

  • To roll back this change, revert the merge with: git revert -m 1 MERGE_SHA and perform another deploy.

URLs

Links to bug tickets or user stories.

QA Plan

Fill in scenarios below in checklist format.
Consider Regression scenarios (did we break something else related to this change) in addition to Happy Path (testing the new feature directly).
Evaluate the risk level and label accordingly and ensure the QA Plan matches the risk level!

  • Clone this repo and switch to this branch.
  • Switch an existing rails app to point the rolypoly gem to this branch.
  • Verify your existing endpoints still work for permissions checks.
  • Verify you can add an allow_by_resource and a role_resource controller and the role check is working.

Copy link
Contributor

@vivekbisen vivekbisen left a comment

Choose a reason for hiding this comment

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

All in all, I like this approach. Doesn't intervene with the existing logic and the code changes seem a lot safer. My suggestions are merely subjective and optional. With new tests for allow_with_resource method, I think we should be good. Also, I would consider raising an error and force users to define role_resource than building one for them. In the raised error, it might be helpful to mention that if you don't really care for resource for your roles, consider using allow instead of allow_with_resource.

@@ -90,8 +98,8 @@ def try_super(mname)
end
end

def build_gatekeeper(roles, actions)
RoleGatekeeper.new(roles, actions).tap { |gatekeeper|
def build_gatekeeper(roles, actions, require_resource)
Copy link
Contributor

Choose a reason for hiding this comment

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

You could make this false by default and only pass true when you need to. So, def build_gatekeeper(roles, actions, require_resource = false) for definition and have the existing methods the same but call build_gatekeeper roles, nil, true in allow_with_resource method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I couldn't decide if I should default it or not. My thinking it make more sense to explicitly say it's false or true. But I'm not sure if that really helps, so defaulting seems good.

@@ -31,22 +32,23 @@ def to_all
self.all_actions = true
end

def allow?(current_roles, action)
def allow?(current_roles, action, role_resource)
Copy link
Contributor

Choose a reason for hiding this comment

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

You should name this differently if you intend to use the passed in param value in role?. It could get confusing very easily when you have the same name in initializer that sets the self attribute and the same name gets used in other parts of the class as local variable.


def match_roles(check_roles)
def match_roles(check_roles, role_resource)
check_roles = filter_roles_by_resource(check_roles, role_resource) if require_resouce
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you call filter_roles_by_resource only when require_resauce :trollface: is true, you should move the check in the method itself. You'll save yourself from repeated conditional and since require_resauce is already is an accessor, it's available across the class.

subject.to(:admin, :scorekeeper)
end

it_should_behave_like "allow should behave correctly"
Copy link
Contributor

Choose a reason for hiding this comment

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

lol wut!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right???

@jnwaletzko
Copy link
Contributor Author

jnwaletzko commented Dec 6, 2016

@vivekbisen could you take another look? I considered your idea of raising an error but we would have to raise it in the role_gatekeeper and I really don't like it there. I also don't want it to break any existing functionality, so if role_resource is not defined on the controller it won't break anything.

let(:admin_role) do
admin = RoleObject.new(:admin)
allow(admin).to receive(:resource?).and_return true
admin
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if you need the allow here. Did you try moving the allow to your before-block. These let blocks should only execute when called.

let(:scorekeeper_role) { RoleObject.new(:scorekeeper) }
let(:current_user_roles) { [admin_role, scorekeeper_role] }
let(:role_resource) { {resource: 123} }
let(:check_access) { controller_instance.rolypoly_check_role_access! }
Copy link
Contributor

Choose a reason for hiding this comment

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

I like to append ! to my method names when they execute code that uses !. So, check_access!?

subject.publicize(:landing)
allow(controller_instance).to receive(:current_user_roles).and_return(current_user_roles)
allow(controller_instance).to receive(:action_name).and_return(action_name)
allow(controller_instance).to receive(:role_resource).and_return(action_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

describe "#index" do
let(:action_name) { "index" }

it { expect(controller_instance).to_not be_public }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a be_private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@vivekbisen vivekbisen left a comment

Choose a reason for hiding this comment

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

Rest of it looks good. Sorry, these things don't pop up all at once.

@@ -93,6 +93,46 @@ class ProfilesController < ApplicationController
end
```

# Allow roles with a resource
`allow_with_resource` acts similarly to `allow` but requires a resource check to access the endpoint.
Copy link
Contributor

Choose a reason for hiding this comment

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

So, as we discussed, it doesn't really require a resource check. It will check resources against an empty role_resource.

@@ -17,6 +17,10 @@ def self.included(sub)
unless sub.method_defined? :current_user_roles
define_method(:current_user_roles) { [] }
end

unless sub.method_defined? :role_resource
define_method(:role_resource) { [] }
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, you wanna change this to an empty hash.

@@ -4,102 +4,144 @@ module Rolypoly
describe RoleGatekeeper do
let(:roles) { %w[admin scorekeeper] }
let(:actions) { %w[index show] }
let(:resource) { [] }
Copy link
Contributor

Choose a reason for hiding this comment

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

And again, this resource would be a hash.

Copy link
Contributor

@vivekbisen vivekbisen left a comment

Choose a reason for hiding this comment

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

👏 Let's get this moving.

@production-status-check
Copy link

:octocat: Has QA approval

Copy link
Contributor

@vivekbisen vivekbisen left a comment

Choose a reason for hiding this comment

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

:shipit:

@vivekbisen vivekbisen merged commit 8d31502 into master Dec 9, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants