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

Account Roles #23

Open
aaronchi opened this issue May 9, 2011 · 9 comments
Open

Account Roles #23

aaronchi opened this issue May 9, 2011 · 9 comments

Comments

@aaronchi
Copy link

aaronchi commented May 9, 2011

Hi, I'm wondering if there's any way to configure cream with account based roles. For example, in a lot of projects I work on, there is not really a global roles system but roles tied to specific accounts. Usually, this is handled by creating a table that joins user_id, role and account_id.

There are a few rails gems that handle this kind of role configuration like acl9. Their role system allows you to tie a role to a specific object by specifying the object that you are assigning the role to. So in the case of cream, it would look something like this:

@user.add_role :admin, @account

Is there any interest in adding this kind of functionality or some kind of workaround that would allow me to do this now with the tools that cream provides?

@kristianmandrup
Copy link
Owner

Not really sure what by mean with accounts in the scenario that you are describing. Could you be a little more detailed and write a more real lie example that would make me understand it better ;)
I just looked at acl9

  class VerySecretController < ApplicationController
    access_control do
      allow :superadmin
      allow :owner, :of => :secret

      action :index do
        allow anonymous, logged_in
      end

      allow logged_in, :to => :show
      allow :manager, :of => :secret, :except => [:delete, :destroy]
      deny :thiefs
    end
...
end

Not sure what is an account in this example?

But...

For example, you can have role called admin which has all available permissions. Now
you may assign this role to several trusted accounts on your system.

Acl9 also supports the notion of object roles, that is, roles with limited scope.

Imagine we are building a magazine site and want to develop a permission system. So, what roles
and permissions are there?

Journalists should be able to create articles in their section and edit their own articles.

Section editors should be able to edit and delete all articles in their sections and
change the published flag.

Editor-in-chief should be able to change everything.

This can be done with cancan-permits for sure! Also check out licenses (part of cancan-permits). Licenses are kind of permission accounts, a set of permission rules (what can be done on which kind of objects). A Permit is linked to a role and a permit can have one or more licenses.

subject.has_role?(role, object)

That should be read as “Does subject have role on object?”.

Subject is an instance of a User, or Account, or whatever model you use for
authentication.

So as I understand it, Account is just a synonym for a User, the subject of the Role whatever you call it.

This is how you control access using cancan:

subject.can?(:edit, object)

But cream wraps it by assuming subject is the #current_user, thus it becomes simply:

can?(:edit, object)

Interesting idea, can the user administrate the object?:

can?(:admin, object)

I already made a custom addition to cancan with the :own for checking ownership (does the subject reference the user in the user, author or some custom property?)

How would you define the schema in order to link roles to any model class? Please help me out with suggestions. How is it done in ACL9? Cheers!

@kristianmandrup
Copy link
Owner

I think that in the cream/cancan world, what you try to achieve would require custom actions and "action groups" IMO.
So an :admin license could be defined that allow :publish and :maintain actions (custom defined actions) to be applied on a certain kinds of objects (Classes). A Role can be granted this license through a Permit and a user be granted this role.
Then apply the following access logic:

can? :administrate, Project do
...
can? :publish, Project do
   # publish section
end

can? :maintain, Project do
   # maintenance section
end

end 

Then :administrate could somehow be set up to point to a group of actions, in this case :publish and :maintain actions and the can? :administrate test should only be valid if the user can? both :publish and :maintain the object. Nice!

@aaronchi
Copy link
Author

aaronchi commented May 9, 2011

The scenario I'm talking about is for applications where users create an account (usually with a subdomain) with their own members like lighthouse or basecamp. So a user would be an admin in the account they created but maybe a guest or an editor under someone else's account.

One way to set this up would look like this:

class Account < ActiveRecord::Base
  has_many :roles
  has_many :users, :through => :roles
end

class User < ActiveRecord::Base
  has_many :roles
  has_many :accounts, :through => :roles
end

class Role < ActiveRecord::Base
  belongs_to :account
  belongs_to :user
end

Systems like ACL9 account for this by allowing you to add role assignments to objects. So User A can be an admin on Account A and a guest on Account B.

The actual role system in ACL9 is set up a little differently but achieves the same purpose. If you're interested, you can see the schema in this migrationfile for padlock_authorization which uses the same role strategy as ACL9 ( https://github.com/baldwindavid/padlock_authorization/blob/master/generators/padlock/templates/db/migrate/migration.rb ) It seems like this would be possible by providing a new role strategy in cream but the role creation methods would have to account for the object association.

I understand what you are saying with ownership but this is not really a question of ownership on an object. It's about assigning a role to an object vs globally.

@kristianmandrup
Copy link
Owner

To implement this in cancan-permits:

permit/base_permit.rb

def can(action, subject, conditions = nil, &block) rules << rule_class.new(true, action, subject, conditions, block) end def cannot(action, subject, conditions = nil, &block) rules << rule_class.new(false, action, subject, conditions, block) end

Overrides the cancan functionality to create rules based on the actions.

In cancan, see: ability.rb and rule.rb

Looking into these files, it seems like there is no requirement that you have to use only REST action names. Indeed you can also define alias actions and custom actions to suit your needs (I didn't know this!):

ability.rb

   # Alias one or more actions into another one.
    #
    # alias_action :update, :destroy, :to => :modify
    # can :modify, Comment
    #
    # Then :modify permission will apply to both :update and :destroy requests.
    #
    # can? :update, Comment # => true
    # can? :destroy, Comment # => true
    #
    # This only works in one direction. Passing the aliased action into the "can?" call
    # will not work because aliases are meant to generate more generic actions.
    #
    # alias_action :update, :destroy, :to => :modify
    # can :update, Comment
    # can? :modify, Comment # => false
    #
    # Unless that exact alias is used.
    #
    # can :modify, Comment
    # can? :modify, Comment # => true
    #
    # The following aliases are added by default for conveniently mapping common controller actions.
    #
    # alias_action :index, :show, :to => :read
    # alias_action :new, :to => :create
    # alias_action :edit, :to => :update
    #
    # This way one can use params[:action] in the controller to determine the permission.
    def alias_action(*args)
      target = args.pop[:to]
      aliased_actions[target] ||= []
      aliased_actions[target] += args
    end

    # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
    def aliased_actions
      @aliased_actions ||= default_alias_actions
    end

This seems to be precisely what I referred to as custom actions and action groups! Cool :) So this should enable what you want I think.
Hope you can make it work for you. Please keep me notified!
Would be an awesome addition to cream to show how to use this on top of the current system.
I think almost all bases are covered then ;)

@kristianmandrup
Copy link
Owner

"Systems like ACL9 account for this by allowing you to add role assignments to objects. So User A can be an admin on Account A and a guest on Account B."

Not sure I understand this. In your example and migrations file, you only have role on User and Account, not any object as "allowing you to add role assignments to objects" would imply?

You can find me on skype as "mainster-dk" for further dialogue about this ;)
The migration schema you reference can be achieved in cream (rather Roles) with the role strategy :many_roles.
I think you just need to add the M-M relationship between User and Account and you can apply role based logic in any shape or form to both User and Account any way you like. If there are special relationships between them somehow, you have to customize that logic yourself and/or describe how to do it/how you would like it.

@kristianmandrup
Copy link
Owner

If the Account/User schema is a common one for role scenarios, it might make sense to integrate the generation of it into cream through an account generator that can be called by the full_config generator if the --account option is set?

@kristianmandrup
Copy link
Owner

Maybe you mean to say this:

if user.account.can? :administrate, Project

Or

if account.user.can? :administrate, Project

And make this kind of functionality pre-packaged in cream or cancan-permits?

@aaronchi
Copy link
Author

aaronchi commented May 9, 2011

I should just implement this in cancan by itself and see what it entails. For my purposes, this is all happening at the level of roles.

current_user.has_role?(:admin, @account) #does the user have an admin role on this account?

Then all of the helpers and authorization methods would have to follow from that logic based on what account the user was in when they are performing the action.

@kristianmandrup
Copy link
Owner

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

No branches or pull requests

2 participants