Skip to content

Commit

Permalink
bring in upstream changes
Browse files Browse the repository at this point in the history
  • Loading branch information
willrax committed Jul 15, 2016
2 parents e86ff94 + 5fe8a95 commit be40b49
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 49 deletions.
12 changes: 8 additions & 4 deletions README.md
Expand Up @@ -11,12 +11,16 @@ AccessGranted is a multi-role and whitelist based authorization gem for Rails. A
Add the gem to your gemfile:

```ruby
gem 'access-granted', '~> 1.0.0'
gem 'access-granted', '~> 1.1.0'
```
Run the bundle command to install it. Then run the generator:

rails generate access_granted:policy

Add the `policies` (and `roles` if you're using it to split up your roles into files) directories to your autoload paths in `application.rb`:

config.autoload_paths += %W(#{config.root}/app/policies #{config.root}/app/roles)

### Supported Ruby versions

Because it has **zero** runtime dependencies it is guaranteed to work on all major Ruby versions MRI 1.9.3-2.2, Rubinius >= 2.X and JRuby >= 1.7.
Expand All @@ -27,7 +31,7 @@ AccessGranted is meant as a replacement for CanCan to solve major problems:

1. Performance

On average AccessGranted is 50-60% faster in resolving identical permissions and takes less memory.
On average AccessGranted is **20 times faster** in resolving identical permissions and takes less memory.
See [benchmarks](https://github.com/chaps-io/access-granted/blob/master/benchmarks).

2. Roles
Expand All @@ -36,7 +40,7 @@ AccessGranted is meant as a replacement for CanCan to solve major problems:

3. Whitelists

This means that you define what the user **can** do, which results in clean, readable policies regardless of app complexity.
This means that you define what the user can do, which results in clean, readable policies regardless of application complexity.
You don't have to worry about juggling `can`s and `cannot`s in a very convoluted way!

_Note_: `cannot` is still available, but has a very specifc use. See [Usage](#usage) below.
Expand All @@ -45,7 +49,7 @@ AccessGranted is meant as a replacement for CanCan to solve major problems:

Permissions can work on basically any object and AccessGranted is framework-agnostic,
but it has Rails support out of the box. :)
It **does not depend on any libraries**, pure and clean Ruby code. Guaranteed to always work,
It does not depend on any libraries, pure and clean Ruby code. Guaranteed to always work,
even when software around changes.

## Usage
Expand Down
2 changes: 1 addition & 1 deletion access-granted.gemspec
Expand Up @@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |spec|
spec.name = "access-granted"
spec.version = "1.0.4"
spec.version = "1.1.0"
spec.authors = ["Piotrek Okoński"]
spec.email = ["piotrek@okonski.org"]
spec.description = %q{Role based authorization gem}
Expand Down
18 changes: 14 additions & 4 deletions benchmarks/config.rb
Expand Up @@ -2,35 +2,45 @@ class Ability
include CanCan::Ability

def initialize(user)
if user.is_admin == true
if user.is_admin
can :destroy, String
can :foo, Integer
end

if user.is_moderator == true
if user.is_moderator
can :update, String
can :bar, String
end

can :read, String
can :zoom, Integer
can :boom, Hash
can :rub, Fixnum
end
end

class AccessPolicy
include AccessGranted::Policy

def configure(user)
def configure
role :administrator, { is_admin: true } do
can :destroy, String
can :foo, Integer
end

role :moderator, { is_moderator: true } do
can :update, String
can :bar, String
end

role :member do
can :read, String
can :zoom, Integer
can :boom, Hash
can :rub, Fixnum
end
end
end

class User < Struct.new(:is_admin, :is_moderator)
class User < Struct.new(:id, :is_admin, :is_moderator)
end
17 changes: 8 additions & 9 deletions benchmarks/permissions.rb
Expand Up @@ -3,9 +3,9 @@
require 'cancan'
require_relative './config'

admin = User.new(true, false)
mod = User.new(false, true)
user = User.new(false, false)
admin = User.new(1, true, false)
mod = User.new(2, false, true)
user = User.new(3, false, false)

user_policy = AccessPolicy.new(user)
admin_policy = AccessPolicy.new(admin)
Expand All @@ -16,30 +16,29 @@
mod_ability = Ability.new(mod)

Benchmark.ips do |x|
x.config(time: 20, warmup: 2)
x.config(time: 5, warmup: 1)

x.report("ag-admin") do
admin_policy.can?(:read, String)
end

x.report("ag-moderator") do
mod_policy.can?(:read, String)
mod_policy.can?(:bar, String)
end

x.report("ag-user") do
user_policy.can?(:read, String)
user_policy.can?(:zoom, Integer)
end

x.report("cancan-admin") do
admin_ability.can?(:read, String)
end

x.report("cancan-moderator") do
mod_ability.can?(:read, String)
mod_ability.can?(:bar, String)
end

x.report("cancan-user") do
user_ability.can?(:read, String)
user_ability.can?(:zoom, Integer)
end

end
2 changes: 1 addition & 1 deletion lib/access-granted.rb
Expand Up @@ -2,7 +2,7 @@
require "access-granted/policy"
require "access-granted/permission"
require "access-granted/role"
require 'access-granted/rails/controller_methods'
require "access-granted/rails/controller_methods"

module AccessGranted

Expand Down
27 changes: 21 additions & 6 deletions lib/access-granted/policy.rb
@@ -1,10 +1,12 @@
module AccessGranted
module Policy
attr_accessor :roles
attr_accessor :roles, :cache
attr_reader :user

def initialize(user)
def initialize(user, cache_enabled = true)
@user = user
@roles = []
@cache = {}
configure
end

Expand All @@ -17,20 +19,25 @@ def role(name, conditions_or_klass = nil, conditions = nil, &block)
raise DuplicateRole, "Role '#{name}' already defined"
end
r = if conditions_or_klass.is_a?(Class) && conditions_or_klass <= AccessGranted::Role
conditions_or_klass.new(name, conditions, @user, block)
conditions_or_klass.new(name, conditions, user, block)
else
Role.new(name, conditions_or_klass, @user, block)
Role.new(name, conditions_or_klass, user, block)
end
roles << r
r
end

def can?(action, subject = nil)
roles.each do |role|
next unless role.applies_to?(@user)
cache[action] ||= {}
cache[action][subject] ||= check_permission(action, subject)
end

def check_permission(action, subject)
applicable_roles.each do |role|
permission = role.find_permission(action, subject)
return permission.granted if permission
end

false
end

Expand All @@ -44,5 +51,13 @@ def authorize!(action, subject)
end
subject
end

private

def applicable_roles
@applicable_roles ||= roles.select do |role|
role.applies_to?(user)
end
end
end
end
22 changes: 8 additions & 14 deletions lib/access-granted/role.rb
Expand Up @@ -8,7 +8,7 @@ def initialize(name, conditions = nil, user = nil, block = nil)
@conditions = conditions
@block = block
@permissions = []
@permissions_by_action = {}

if @block
instance_eval(&@block)
else
Expand All @@ -28,8 +28,10 @@ def cannot(action, subject, conditions = {}, &block)
end

def find_permission(action, subject)
permissions_by_action(action).detect do |permission|
permission.matches_subject?(subject) && permission.matches_conditions?(subject)
permissions.detect do |permission|
permission.action == action &&
permission.matches_subject?(subject) &&
permission.matches_conditions?(subject)
end
end

Expand All @@ -52,17 +54,15 @@ def matches_hash?(user, conditions = {})

def add_permission(granted, action, subject, conditions, block)
prepare_actions(action).each do |a|
raise DuplicatePermission if permission_exists?(a, subject)
@permissions << Permission.new(granted, a, subject, @user, conditions, block)
@permissions_by_action[a] ||= []
@permissions_by_action[a] << @permissions.size - 1
raise DuplicatePermission if find_permission(a, subject)
permissions << Permission.new(granted, a, subject, @user, conditions, block)
end
end

private

def permission_exists?(action, subject)
permissions_by_action(action).any? do |permission|
permissions.any? do |permission|
permission.matches_subject?(subject)
end
end
Expand All @@ -74,11 +74,5 @@ def prepare_actions(action)
actions = Array(*[action])
end
end

def permissions_by_action(action)
(@permissions_by_action[action] || []).map do |index|
@permissions[index]
end
end
end
end
16 changes: 8 additions & 8 deletions spec/policy_spec.rb
Expand Up @@ -6,16 +6,16 @@

describe "#configure" do
before :each do
@member = double("member", id: 1, is_moderator: false, is_admin: false, is_banned: false)
@mod = double("moderator", id: 2, is_moderator: true, is_admin: false, is_banned: false)
@admin = double("administrator", id: 3, is_moderator: false, is_admin: true, is_banned: false)
@banned = double("banned", id: 4, is_moderator: false, is_admin: true, is_banned: true)
@member = FakeUser.new(1, false, false, false)
@mod = FakeUser.new(2, true, false, false)
@admin = FakeUser.new(3, false, true, false)
@banned = FakeUser.new(4, false, true, true)
end

it "passes user object to permission block" do
post_owner = double(id: 123)
other_user = double(id: 5)
post = FakePost.new(post_owner.id)
post = FakePost.new(1, post_owner.id)

klass = Class.new do
include AccessGranted::Policy
Expand Down Expand Up @@ -81,8 +81,8 @@ def configure(user)

context "when multiple roles define the same permission" do
it "checks all roles until conditions are met" do
user_post = FakePost.new(@member.id)
other_post = FakePost.new(66)
user_post = FakePost.new(1, @member.id)
other_post = FakePost.new(2, 66)

klass = Class.new do
include AccessGranted::Policy
Expand Down Expand Up @@ -138,7 +138,7 @@ def configure
end
end
expect(klass.new(@member).can?(:create, String)).to eq(true)
expect(klass.new(@banned).cannot?(:create, String)).to eq(true)
expect(klass.new(@banned).can?(:create, String)).to eq(false)
end
end

Expand Down
6 changes: 4 additions & 2 deletions spec/spec_helper.rb
Expand Up @@ -23,10 +23,12 @@ def self.helper_method(*args)

require 'access-granted'

class FakePost < Struct.new(:user_id)
class FakeUser < Struct.new(:id, :is_moderator, :is_admin, :is_banned)
end

class FakePost < Struct.new(:id, :user_id)
end

class AccessPolicy
include AccessGranted::Policy
end

0 comments on commit be40b49

Please sign in to comment.