项目作者: xiejiangzhi

项目描述 :
Permission manage center
高级语言: Ruby
项目地址: git://github.com/xiejiangzhi/seven.git
创建时间: 2017-02-14T04:16:12Z
项目社区:https://github.com/xiejiangzhi/seven

开源协议:MIT License

下载


Seven

Define and verify Permissions.

Build Status
Gem Version

Installation

Add this line to your application’s Gemfile:

  1. gem 'sevencan'

And then execute:

  1. $ bundle

Or install it yourself as:

  1. $ gem install sevencan

Usage

Create your manager

  1. $abilities_manager = Seven::Manager.new

You can put it to config/initializers/abilities.rb if you on Rails

Define your rules

On Rails, we can save these rules as app/abilities/*_abilities.rb

A simple example

  1. class TopicAbilities
  2. include Seven::Abilities
  3. # it has some instance methods:
  4. # current_user: a user instance of nil
  5. # target: verify current_user permissions with this target. It's a Topic or a instance of Topic here
  6. # if target if Topic or a instance of Topic, we will use this class to verify ability
  7. $abilities_manager.define_rules(Topic, TopicAbilities)
  8. # anyone can read list of topics(index action), non-login user also can read it
  9. abilities do
  10. can :read_topics
  11. end
  12. # define some abilities if the user logined
  13. abilities pass: Proc.new { current_user } do
  14. # user can read show page and create a new topic.(show, new and create action)
  15. can :read_topic, :create_topic
  16. subject = target.is_a?(Topic) ? Topic.new : target # Maybe the target is a Topic class
  17. # user can edit and destroy own topic.(edit, update and destroy action)
  18. can :edit_topic, :delete_topic if subject.user_id == current_user.id
  19. end
  20. # define some abilities if current_user is admin. `%w{admin}.include?(current_user.role)`
  21. abilities check: :role, in: %w{admin} do
  22. # admin can edit and delete all topics
  23. can :edit_topic, :delete_topic
  24. end
  25. end

You also can write saome complex rules

  1. # Topic and Topic instances
  2. class TopicAbilities
  3. include Seven::Abilities
  4. $abilities_manager.define_rules(Topic, TopicAbilities)
  5. # we will define some abilities for any user(user instance or nil) in this block
  6. abilities do
  7. can(:read_topic)
  8. can_manager_topic if target_topic.user_id == current_user.id
  9. cannot_manager_topic if target_topic.is_lock
  10. end
  11. # if current user(current_user isn't nil) and %i{admin editor}.include?(current_user.role)
  12. # we will define some abilities for the user
  13. abilities check: :role, in: %i{admin editor} do
  14. can_manager_topic
  15. end
  16. # current_user.role is :reviewer
  17. abilities check: :role, equal: :reviewer do
  18. can :review_topic
  19. end
  20. # Of course, you also can use your rule.
  21. # For example, we will give current_user some abilities if our proc doesn't return a false or nil value
  22. abilities pass: Proc.new { current_user && target.user_id.nil? } do
  23. can_manager_topic
  24. end
  25. # And you can move that proc to a instance method
  26. abilities pass: :my_filter do
  27. end
  28. def can_manager_topic
  29. can :edit_topic, :destroy_topic
  30. end
  31. def cannot_manager_topic
  32. cannot :edit_topic, :destroy_topic
  33. end
  34. def my_filter
  35. current_user && target.user_id.nil?
  36. end
  37. end

You can define some abilities for all objects

  1. # for all objects, they're global rules
  2. $abilities_manager.define_rules(Object, YourAbilities)

Use a block to define some abilities

  1. $abilities_manager.define_rules(User) do
  2. can(:read_user)
  3. if current_user
  4. can(:edit_user) if target.id == current_user.id
  5. can(:destroy_user) if current_user.is_admin?
  6. end
  7. end

Verify user abilities

No target

  1. manager.define_rules(Object) { can :read_topics }
  2. manager.can?(current_user, :read_topics, nil) # true, target is nil
  3. manager.can?(nil, :read_topics) # true, anyone can read_topics
  4. manager.can?(current_user, :read_user) # false, we didn't define this abilities
  5. manager.can?(current_user, :edit_user) # false
  6. manager.store.add(user.id, :edit_user, true)
  7. manager.can?(current_user, :edit_user) # true
  8. manager.can?(nil, :edit_user) # true

Verify abilities for a class or its instances

  1. manager.define_rules(Topic) { can :read_topics }
  2. manager.can?(nil, :read_topics, Topic) # true, for Topic class
  3. manager.can?(nil, :read_topics, Topic.first) # true, for instance of Topic
  4. manager.can?(current_user, :read_topics, Topic.first) # true
  5. manager.can?(current_user, :read_topics) # false
  6. manager.can?(nil, :read_topics) # false, it's target is nil, it isn't a topic
  7. manager.store.add(user.id, :edit_user, true)
  8. manager.can?(current_user, :edit_user, User) # true
  9. manager.can?(current_user, :edit_user, User.first) # true
  10. manager.can?(current_user, :edit_user) # true
  11. manager.can?(nil, :edit_user) # false

Define and verify abilities for a instance(TODO)

  1. manager.define_rules(Topic.first) { can :read_topics }
  2. manager.can?(nil, :read_topics, Topic) # false
  3. manager.can?(nil, :read_topics, Topic.first) # true
  4. manager.can?(current_user, :read_topics, Topic.first) # true
  5. manager.can?(current_user, :read_topics, Topic.last) # false
  6. manager.can?(current_user, :read_topics) # false
  7. manager.can?(nil, :read_topics) # false

Rails

Init your manager

in config/initializers/seven_abilities.rb

  1. $abilities_manager = Seven::Manager.new
  2. Dir[Rails.root.join('app/abilities/**/*.rb')].each { |file| require file }

Define some rules in app/abilities/*.rb

  1. class MyAbilities
  2. include Seven::Abilities
  3. $abilities_manager.define_rules(MyObject, MyAbilities)
  4. # define some rules
  5. end"

ControllerHelpers

We need these methods of controller to check user ability

  • current_user: It is MyAbilities#current_user
  • abilities_manager: You need return a instance of Seven::Manager
  • ability_check_callback: We will call it after verifying

For example:

  1. class ApplicationController < ActionController::Base
  2. # when you include `Seven::Rails::ControllerHelpers` module, it will do something below
  3. # define `can?` instance method and `seven_ability_check` methods for your controller
  4. # define `seven_ability_check_filter` instance method, it's callback of before_action for Seven
  5. # define `seven_ability_check` class methods, it will call `before_action :seven_ability_check_filter` and store some your options
  6. include Seven::Rails::ControllerHelpers
  7. def abilities_manager
  8. $abilities_manager
  9. end
  10. def ability_check_callback(is_allowed, ability, target)
  11. # is_allowed: true or false, is_allowed is true when user can access this action
  12. # ability: ability of this action, like :read_topic
  13. # target: resource object of this action
  14. redirect_to root_path notice: 'Permission denied' unless is_allowed
  15. end
  16. end

Verify permissions for default actions, we will get a ability name according to controller name.

Some mapping examples:

  • TopicController#index => :read_topics
  • TopicController#show => :read_topic
  • UserController#new => :create_user
  • UserController#create => :create_user
  • UserController#edit => :edit_user
  • UserController#update => :edit_user
  • UserController#destroy => :delete_user
  1. class TopicController < ApplicationController
  2. before_action :find_topic
  3. # if exists @topic, target is @topic, else use the result of proc, use Topic if the proc return nil
  4. seven_ability_check [:@topic, Proc.new { nil }, Topic]
  5. # Seven will automitically checks current_user has read_topics of Topic
  6. # We have no @topic and proc is nil, the Topic is our target
  7. def index
  8. end
  9. # check current_user can read_topic of @topic
  10. def show
  11. end
  12. private
  13. def find_topic
  14. @topic = Topic.find(params[:id])
  15. end
  16. end

Set a customized ability for actions

  1. class TopicController < ApplicationController
  2. before_action :find_topic
  3. # if exist @topic, target is @topic, else use Topic
  4. seven_ability_check(
  5. [:@topic, Topic], # default targets
  6. my_action1: {ability: :custom_ability}, # check :custom_ability and use default targets
  7. my_action2: {ability: :custom_ability, target: [:@my_target]} # check :custom_ability and use :@my_target
  8. )
  9. # or
  10. # seven_ability_check(
  11. # index: {ability: :read_my_ability, target: SuperTopic},
  12. # my_action1: {ability: :custom_ability1},
  13. # my_action2: {ability: :custom_ability2, target: [:@my_target]}
  14. # )
  15. def index
  16. end
  17. def my_action1
  18. end
  19. def my_action2
  20. end
  21. private
  22. def find_topic
  23. @topic = Topic.find(params[:id])
  24. end
  25. end

Use a customize resource name, we will get ability according to this suffix

  1. class TopicController < ApplicationController
  2. before_action :find_topic
  3. seven_ability_check [:@topic, Topic], nil, resource_name: :comment
  4. # auto check current_user allow read_comments of Topic
  5. def index
  6. end
  7. # auto check current_user allow read_comment of @topic
  8. def show
  9. end
  10. # Other actions:
  11. # new: create_comment of Topic
  12. # create: create_comment of Topic
  13. # edit: edit_comment of @topic
  14. # update: edit_comment of @topic
  15. # destroy: delete_comment of @topic
  16. private
  17. def find_topic
  18. @topic = Topic.find(params[:id])
  19. end
  20. end

Manually check, don’t call ability_check_callback

  1. class TopicController < ApplicationController
  2. before_action :find_topic
  3. skip_before_action :seven_ability_check_filter
  4. def my_action1
  5. raise 'no permission' unless can?(:read_something, @topic)
  6. # my codes
  7. end
  8. private
  9. def find_topic
  10. @topic = Topic.find(params[:id])
  11. end
  12. end

Skip some actions

  1. class TopicController < ApplicationController
  2. before_action :find_topic
  3. skip_before_filter :seven_ability_check_filter, only: :index
  4. def index
  5. if page_no > 1
  6. ability_check_callback(can?(:read_something, @topic), :read_something, @topic)
  7. end
  8. end
  9. private
  10. def find_topic
  11. @topic = Topic.find(params[:id])
  12. end
  13. end

Dynamic abilities

Store

  1. manager = Seven::Manager.new # read/write dynamic abilities from memory store
  2. # or
  3. manager = Seven::Manager.new(store: {redis: Redis.current}) # read/write dynamic abilities from Redis
  4. # or
  5. manager = Seven::Manager.new(store: MyStore.new) # read/write from your store, Seven just access MyStore#list methods

Create your store

  1. # columns: user_id: integer, ability: string, status: boolean
  2. class Ability < ActiveRecord::Base
  3. def self.list(user)
  4. where(user_id: user.id).each_with_object({}) do |record, result|
  5. result[record.ability.to_sym] = record.status # true or false
  6. end
  7. end
  8. end
  9. Seven::Manager.new(store: Ability)
  10. # then, you can add some abilities for a user:
  11. Ability.create(user: user, ability: :read_user, status: true)

Define some dynamic rules for system store

  1. $abilities_manager.store.add(user.id, :edit_user, true)
  2. $abilities_manager.store.add(user.id, :create_user, false)
  3. $abilities_manager.store.list(user.id) # {edit_user: true, create_user: false}
  4. $abilities_manager.can?(user, :create_user, nil) # false
  5. $abilities_manager.can?(user, :edit_user, nil) # true
  6. $abilities_manager.store.del(user.id, :edit_user)
  7. $abilities_manager.store.list(user.id) # {create_user: false}

RSpec Testing

in spec/rails_helper.rb or spec/spec_helper.rb

  1. require 'seven/rspec'

Write some abilities testing

  1. RSpec.describe UserAbilities do
  2. it 'should can read topic' do
  3. # expect([current_user, target]).to abilities_eql([:read_topic])
  4. expect([user, topic]).to abilities_eql([:read_topic])
  5. end
  6. it 'should can manager topic' do
  7. expect([admin_user, topic]).to abilities_eql([:read_topic, :edit_topic, :destroy_topic])
  8. end
  9. end

TODO

  • Dynamic rule for a record

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/xiejiangzhi/seven. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.