aclatraz 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ *.rdb
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kriss 'nu7hatch' Kowalik
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,107 @@
1
+ = ACLatraz
2
+
3
+ Extremaly fast and flexible access control mechanism inspired by *nix ACLs,
4
+ powered by fast key value stores like Redis or TokyoCabinet.
5
+
6
+ == Installation
7
+
8
+ You can install ACLatraz via rubygems:
9
+
10
+ sudo gem install ACLatraz
11
+
12
+ == Basic usage
13
+
14
+ First of all you have to setup store for ACL data. ACLatraz for now uses only
15
+ Redis database for storage. Store can be set like below:
16
+
17
+ Aclatraz.store :redis, "redis://localhost:6379/0"
18
+
19
+ Then you have to define your suspects:
20
+
21
+ class Account < ActiveRecord::Base
22
+ include Aclatraz::Suspect
23
+ end
24
+
25
+ Now you suspect have few methods which will helps you with managing permissions:
26
+
27
+ @account = Account.create
28
+ @account.has_role?(:foo) # => false
29
+ @account.is.foo? # syntactic sugar for #has_role?
30
+ @account.assign_role!(foo) # or @account.is.foo!
31
+ @account.is.foo? # => true
32
+ @account.delete_role!(foo) # or @account.is_not.foo!
33
+ @account.is.foo? # => false
34
+ @account.is_not.foo? # => true
35
+
36
+ Last step is create you access control list and set guards:
37
+
38
+ class Foo
39
+ include Aclatraz::Guard
40
+
41
+ suspect :account do
42
+ allow all
43
+
44
+ on :manage do
45
+ allow :root
46
+ allow :manager
47
+ end
48
+
49
+ on :delete do
50
+ deny :manager
51
+ allow :owner_of => "@project"
52
+ end
53
+ end
54
+
55
+ # The #suspect method allows to pass String, Symbol or Object as suspect.
56
+ # When String given (eg. "account" or "@account") then value of given
57
+ # instance variable will be treated as suspect. When Symbol given, then
58
+ # system will take value from given instance method. Otherwise given object
59
+ # will be treated as suspect if possible.
60
+ #
61
+ # Similar situation is with permission arguments. To #allow and #deny
62
+ # methods you can pass symbol or hash. Symbol given represents
63
+ # single role, the hash represents ownership of assigned object or class.
64
+ # Assigned object declaration behaves the same as suspects' one.
65
+
66
+ def account
67
+ @account ||= Account.find(ENV['MYAPP_ACCOUNT'])
68
+ end
69
+
70
+ def index
71
+ guard!
72
+ # ... everybody are allowed to see this
73
+ end
74
+
75
+ def create
76
+ guard!(:manage)
77
+ # ... only managers and root will see this
78
+ end
79
+
80
+ def delete(id)
81
+ @product = Product.find(id)
82
+ guard!(:manage, :delete)
83
+ # ... only root or or @product owner will see this
84
+ end
85
+
86
+ def product(id)
87
+ @product.find(id)
88
+ account.is.owner_of?(@product) do
89
+ return @product
90
+ end
91
+ return nil
92
+ end
93
+ end
94
+
95
+ == Note on Patches/Pull Requests
96
+
97
+ * Fork the project.
98
+ * Make your feature addition or bug fix.
99
+ * Add tests for it. This is important so I don't break it in a
100
+ future version unintentionally.
101
+ * Commit, do not mess with rakefile, version, or history.
102
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
103
+ * Send me a pull request. Bonus points for topic branches.
104
+
105
+ == Copyright
106
+
107
+ Copyright (c) 2010 Kriss 'nu7hatch' Kowalik. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "aclatraz"
8
+ gem.summary = %Q{Flexible access control that doesn't sucks!}
9
+ gem.description = %Q{Extremaly fast and flexible access control mechanism inspired by *nix ACLs, powered by fast key value stores like Redis or TokyoCabinet.}
10
+ gem.email = "kriss.kowalik@gmail.com"
11
+ gem.homepage = "http://github.com/nu7hatch/aclatraz"
12
+ gem.authors = ["Kriss 'nu7hatch' Kowalik"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "mocha", ">= 0.9"
15
+ gem.add_development_dependency "redis", "~> 2.0"
16
+ #gem.add_development_dependency "tokyocabinet", ">= XX"
17
+ gem.add_dependency "dictionary", "~> 1.0"
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :spec => :check_dependencies
37
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "ACLatraz #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/aclatraz.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'dictionary'
2
+
3
+ require 'aclatraz/helpers'
4
+ require 'aclatraz/store'
5
+ require 'aclatraz/acl'
6
+ require 'aclatraz/guard'
7
+ require 'aclatraz/suspect'
8
+
9
+ module Aclatraz
10
+ class AccessDenied < Exception; end
11
+ class InvalidSuspect < ArgumentError; end
12
+ class InvalidPermission < ArgumentError; end
13
+ class InvalidStore < ArgumentError; end
14
+ class StoreNotInitialized < Exception; end
15
+
16
+ extend Helpers
17
+
18
+ def self.store(klass=nil, *args)
19
+ if klass
20
+ begin
21
+ klass = eval("Aclatraz::Store::#{camelize(klass.to_s)}") unless klass.is_a?(Class)
22
+ @store = klass.new(*args)
23
+ rescue NameError
24
+ raise InvalidStore, "The #{klass.inspect} ACL store is not defined!"
25
+ end
26
+ else
27
+ @store or raise StoreNotInitialized, "ACL store is not initialized!"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ module Aclatraz
2
+ class ACL
3
+ class Action
4
+ attr_reader :permissions
5
+
6
+ def initialize(parent, &block)
7
+ @parent = parent
8
+ @permissions = Dictionary.new
9
+ instance_eval(&block)
10
+ end
11
+
12
+ def allow(permission)
13
+ @permissions[permission] = true
14
+ end
15
+
16
+ def deny(permission)
17
+ @permissions[permission] = false
18
+ end
19
+
20
+ def all
21
+ true
22
+ end
23
+
24
+ def on(*args, &block)
25
+ @parent.on(*args, &block)
26
+ end
27
+ end
28
+
29
+ attr_reader :actions
30
+
31
+ def initialize(&block)
32
+ @actions = {}
33
+ on(:_, &block)
34
+ end
35
+
36
+ def permissions
37
+ @actions[:_] ? @actions[:_].permissions : Dictionary.new
38
+ end
39
+
40
+ def [](action)
41
+ @actions[action]
42
+ end
43
+
44
+ def on(action, &block)
45
+ raise ArgumentError, "No block given" unless block_given?
46
+ if @actions.key?(action)
47
+ @actions[action].instance_eval(&block)
48
+ else
49
+ @actions[action] = Action.new(self, &block)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,87 @@
1
+ module Aclatraz
2
+ module Guard
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ base.send :include, InstanceMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ attr_reader :acl_suspect
10
+ attr_reader :acl_permissions
11
+
12
+ def suspects(name, &block)
13
+ @acl_suspect = name
14
+ @acl_permissions = ACL.new(&block)
15
+ end
16
+ alias_method :access_control, :suspects
17
+ end
18
+
19
+ module InstanceMethods
20
+ def current_suspect
21
+ case self.class.acl_suspect
22
+ when Symbol
23
+ @current_suspect ||= send(self.class.acl_suspect)
24
+ when String
25
+ @current_suspect ||= instance_variable_get("@#{self.class.acl_suspect}")
26
+ else
27
+ @current_suspect ||= self.class.acl_suspect
28
+ end
29
+ end
30
+
31
+ def guard!(*actions)
32
+ if current_suspect.respond_to?(:acl_suspect?)
33
+ actions.unshift(:_)
34
+ authorized = false
35
+ permissions = Dictionary.new
36
+
37
+ actions.each do |action|
38
+ self.class.acl_permissions.actions[action].permissions.each_pair do |key, value|
39
+ permissions.delete(key)
40
+ permissions.push(key, value)
41
+ end
42
+ end
43
+
44
+ permissions.each do |permission, allow|
45
+ has_permission = check_permission(permission)
46
+ if permission == true
47
+ authorized = allow ? true : false
48
+ next
49
+ end
50
+ if allow
51
+ authorized ||= has_permission
52
+ else
53
+ authorized = false if has_permission
54
+ end
55
+ end
56
+
57
+ raise Aclatraz::AccessDenied unless authorized
58
+ true
59
+ else
60
+ raise Aclatraz::InvalidSuspect
61
+ end
62
+ end
63
+ alias_method :authorize!, :guard!
64
+
65
+ def check_permission(permission)
66
+ case permission
67
+ when String, Symbol, true
68
+ current_suspect.has_role?(permission)
69
+ when Hash
70
+ permission.each do |role, object|
71
+ case object
72
+ when String
73
+ object = "@#{object}" unless object[0] == "@"
74
+ object = instance_variable_get(object)
75
+ when Symbol
76
+ object = send(object)
77
+ end
78
+ return true if current_suspect.has_role?(role, object)
79
+ end
80
+ false
81
+ else
82
+ raise Aclatraz::InvalidPermission
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,18 @@
1
+ module Aclatraz
2
+ module Helpers
3
+ def pack(prefix, object=nil)
4
+ case object
5
+ when nil
6
+ "#{prefix}"
7
+ when Class
8
+ "#{prefix}/#{object.name}"
9
+ else
10
+ "#{prefix}/#{object.class.name}/#{object.id}"
11
+ end
12
+ end
13
+
14
+ def camelize(str)
15
+ str.split('_').map {|w| w.capitalize}.join
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module Aclatraz
2
+ module Store
3
+ #autoload :Memcached, 'aclatraz/store/memcached'
4
+ autoload :Redis, 'aclatraz/store/redis'
5
+ #autoload :TokyoCabinet, 'aclatraz/store/tokyocabinet'
6
+
7
+ class Base
8
+ include Aclatraz::Helpers
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ begin
2
+ require 'redis'
3
+ rescue LoadError
4
+ raise "You must install redis to use the Redis store backend"
5
+ end
6
+
7
+ module Aclatraz
8
+ module Store
9
+ class Redis < Base
10
+ ROLES_KEY = "aclatraz.roles"
11
+ MEMBER_ROLES_KEY = "member.%s.roles"
12
+
13
+ def initialize(*args)
14
+ @backend = ::Redis.new(*args)
15
+ end
16
+
17
+ def set(role, owner, object=nil)
18
+ @backend.multi do
19
+ unless object
20
+ @backend.hset(ROLES_KEY, role, 1)
21
+ @backend.hset(MEMBER_ROLES_KEY % owner.id.to_s, role, 1)
22
+ end
23
+ @backend.sadd(role.to_s, pack(owner.id, object))
24
+ end
25
+ end
26
+
27
+ def permissions(role)
28
+ @backend.smembers(role.to_s)
29
+ end
30
+
31
+ def roles(member=nil)
32
+ if member
33
+ @backend.hkeys(MEMBER_ROLES_KEY % member.id.to_s)
34
+ else
35
+ @backend.hkeys(ROLES_KEY)
36
+ end
37
+ end
38
+
39
+ def check(role, owner, object=nil)
40
+ @backend.sismember(role.to_s, pack(owner.id, object))
41
+ end
42
+
43
+ def delete(role, owner, object=nil)
44
+ @backend.srem(role.to_s, pack(owner.id, object))
45
+ end
46
+
47
+ def clear
48
+ @backend.flushdb
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,90 @@
1
+ module Aclatraz
2
+ module Suspect
3
+ class SemanticRoles
4
+ class Base
5
+ ROLE_SUFFIXES = /(_(of|at|on|by|for|in))?(\?|\!)\Z/
6
+
7
+ attr_reader :suspect
8
+
9
+ def initialize(suspect)
10
+ @suspect = suspect
11
+ end
12
+ end
13
+
14
+ class Yes < Base
15
+ def method_missing(meth, *args, &blk)
16
+ meth = meth.to_s
17
+ if meth =~ ROLE_SUFFIXES
18
+ setter = meth[-1].chr == "!"
19
+ role = meth.gsub(ROLE_SUFFIXES, '')
20
+ if setter
21
+ suspect.assign_role!(*args.unshift(role))
22
+ else
23
+ authorized = suspect.has_role?(*args.unshift(role.to_sym))
24
+ blk.call if authorized && block_given?
25
+ authorized
26
+ end
27
+ else
28
+ # super doesn't work here, so...
29
+ raise NoMethodError, "undefined local variable or method method `#{meth}' for #{inspect}:#{self.class.name}"
30
+ end
31
+ end
32
+ end
33
+
34
+ class Not < Base
35
+ def method_missing(meth, *args, &blk)
36
+ meth = meth.to_s
37
+ if meth =~ ROLE_SUFFIXES
38
+ deleter = meth[-1].chr == "!"
39
+ role = meth.gsub(ROLE_SUFFIXES, '')
40
+ if deleter
41
+ suspect.delete_role!(*args.unshift(role))
42
+ else
43
+ authorized = suspect.has_role?(*args.unshift(role.to_sym))
44
+ blk.call if !authorized && block_given?
45
+ !authorized
46
+ end
47
+ else
48
+ raise NoMethodError, "undefined local variable or method method `#{meth}' for #{inspect}:#{self.class.name}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.included(base)
55
+ base.send :include, InstanceMethods
56
+ end
57
+
58
+ module InstanceMethods
59
+ ACL_ROLE_SUFFIXES = /(_(of|at|on|by|for|in))?\Z/
60
+
61
+ def acl_suspect?
62
+ true
63
+ end
64
+
65
+ def has_role?(role, object=nil)
66
+ Aclatraz.store.check(role.to_s.gsub(ACL_ROLE_SUFFIXES, ''), self, object)
67
+ end
68
+
69
+ def assign_role!(role, object=nil)
70
+ Aclatraz.store.set(role.to_s.gsub(ACL_ROLE_SUFFIXES, ''), self, object)
71
+ end
72
+
73
+ def delete_role!(role, object=nil)
74
+ Aclatraz.store.delete(role.to_s.gsub(ACL_ROLE_SUFFIXES, ''), self, object)
75
+ end
76
+
77
+ def roles
78
+ Aclatraz.store.roles(self)
79
+ end
80
+
81
+ def is
82
+ @acl_is ||= SemanticRoles::Yes.new(self)
83
+ end
84
+
85
+ def is_not
86
+ @acl_is_not ||= SemanticRoles::Not.new(self)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aclatraz ACL" do
4
+ before(:all) { Aclatraz.store(:redis, "redis://localhost:6379/0") }
5
+
6
+ it "should properly store flat access control lists" do
7
+ acl = Aclatraz::ACL.new {}
8
+ acl.actions[:_].allow :foo
9
+ acl.permissions[:foo].should be_true
10
+ acl.actions[:_].deny :foo
11
+ acl.permissions[:foo].should be_false
12
+ acl.actions[:_].allow :foo => :bar
13
+ acl.permissions[{:foo=>:bar}].should be_true
14
+ end
15
+
16
+ it "should allow for define seperated lists which are inherit from main block" do
17
+ acl = Aclatraz::ACL.new do
18
+ allow :foo
19
+ on(:spam) { allow :spam }
20
+ on(:eggs) { allow :eggs }
21
+ on(:spam) { allow :boo }
22
+ end
23
+
24
+ acl.permissions[:foo].should be_true
25
+ acl.permissions[:spam].should_not be_true
26
+ acl.permissions[:eggs].should_not be_true
27
+ acl.permissions[:boo].should_not be_true
28
+ acl[:spam].permissions[:foo].should be_nil
29
+ acl[:spam].permissions[:spam].should be_true
30
+ acl[:spam].permissions[:eggs].should be_nil
31
+ acl[:spam].permissions[:boo].should be_true
32
+ acl[:eggs].permissions[:foo].should be_nil
33
+ acl[:eggs].permissions[:eggs].should be_true
34
+ acl[:eggs].permissions[:spam].should be_nil
35
+ end
36
+
37
+ it "should raise ArgumentError when no block given" do
38
+ lambda { Aclatraz::ACL.new }.should raise_error(ArgumentError)
39
+ end
40
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aclatraz guard" do
4
+ include Aclatraz::Guard
5
+
6
+ before(:all) { Aclatraz.store(:redis, "redis://localhost:6379/0") }
7
+ let(:foo) { @foo ||= StubSuspect.new }
8
+ let(:target) { StubTarget.new }
9
+
10
+ suspects :foo do
11
+ allow :role1
12
+ deny :role2
13
+ on :bar do
14
+ allow :role3
15
+ deny :role4 => StubTarget
16
+ end
17
+ on :bla do
18
+ deny :role3
19
+ allow :role2 => :target
20
+ allow :role6 => 'bar'
21
+ end
22
+ on :deny_all do
23
+ deny all
24
+ end
25
+ on :allow_all do
26
+ allow all
27
+ end
28
+ allow :role5
29
+ end
30
+
31
+ it "should properly store name of suspected object" do
32
+ self.class.acl_suspect.should == :foo
33
+ end
34
+
35
+ it "should properly store permissions" do
36
+ self.class.acl_permissions.should be_kind_of(Aclatraz::ACL)
37
+ end
38
+
39
+ it "should properly guard permissions" do
40
+ access_denied = Aclatraz::AccessDenied
41
+
42
+ lambda { guard! }.should raise_error(access_denied)
43
+ foo.is.role1!
44
+ lambda { guard! }.should_not raise_error(access_denied)
45
+ foo.is.role2!
46
+ lambda { guard! }.should raise_error(access_denied)
47
+ foo.is.role5!
48
+ lambda { guard! }.should_not raise_error(access_denied)
49
+
50
+ lambda { guard!(:bar) }.should_not raise_error(access_denied)
51
+ foo.is_not.role5!
52
+ lambda { guard!(:bar) }.should raise_error(access_denied)
53
+ foo.is_not.role2!
54
+ lambda { guard!(:bar) }.should_not raise_error(access_denied)
55
+ foo.is_not.role1!
56
+ lambda { guard!(:bar) }.should raise_error(access_denied)
57
+ foo.is.role3!
58
+ lambda { guard!(:bar) }.should_not raise_error(access_denied)
59
+ foo.is.role4!(StubTarget)
60
+ lambda { guard!(:bar) }.should raise_error(access_denied)
61
+
62
+ lambda { guard!(:bla) }.should raise_error(access_denied)
63
+ foo.is_not.role3!
64
+ foo.is.role1!
65
+ lambda { guard!(:bla) }.should_not raise_error(access_denied)
66
+ foo.is_not.role1!
67
+ lambda { guard!(:bla) }.should raise_error(access_denied)
68
+ foo.is.role2!(target)
69
+ lambda { guard!(:bla) }.should_not raise_error(access_denied)
70
+ foo.is_not.role2!(target)
71
+ @bar = StubTarget.new
72
+ foo.is.role6!(@bar)
73
+ lambda { guard!(:bla) }.should_not raise_error(access_denied)
74
+ foo.is.role3!
75
+ lambda { guard!(:bla) }.should_not raise_error(access_denied)
76
+ foo.is_not.role6!(@bar)
77
+ foo.is.role5!
78
+ lambda { guard!(:bla) }.should raise_error(access_denied)
79
+ foo.is_not.role3!
80
+ lambda { guard!(:bla) }.should_not raise_error(access_denied)
81
+
82
+ foo.is_not.role5!
83
+ foo.is_not.role4!(StubTarget)
84
+ lambda { guard!(:bar, :bla) }.should raise_error(access_denied)
85
+ foo.is.role1!
86
+ lambda { guard!(:bar, :bla) }.should_not raise_error(access_denied)
87
+ foo.is.role4!(StubTarget)
88
+ lambda { guard!(:bar, :bla) }.should raise_error(access_denied)
89
+ foo.is.role2!(target)
90
+ lambda { guard!(:bar, :bla) }.should_not raise_error(access_denied)
91
+
92
+ lambda { guard!(:allow_all) }.should_not raise_error(access_denied)
93
+ lambda { guard!(:deny_all) }.should raise_error(access_denied)
94
+
95
+ lambda { guard!(:bar, :allow_all, :bla) }.should_not raise_error(access_denied)
96
+ foo.is_not.role2!(target)
97
+ lambda { guard!(:bar, :allow_all, :bla) }.should_not raise_error(access_denied)
98
+ foo.is.role3!
99
+ lambda { guard!(:bar, :allow_all, :bla) }.should raise_error(access_denied)
100
+
101
+ foo.is_not.role3!
102
+ lambda { guard!(:bar, :deny_all, :bla) }.should raise_error(access_denied)
103
+ foo.is.role2!(target)
104
+ lambda { guard!(:bar, :deny_all, :bla) }.should_not raise_error(access_denied)
105
+ end
106
+
107
+ describe "ivalid permission" do
108
+ suspects(:foo) { allow Object.new }
109
+
110
+ it "#check_permissions should raise InvalidPermission error" do
111
+ lambda { guard! }.should raise_error(Aclatraz::InvalidPermission)
112
+ end
113
+ end
114
+
115
+ describe "invalid suspect" do
116
+ suspects('bar') { }
117
+
118
+ it "#guard! should raise InvalidSuspect error" do
119
+ lambda { guard! }.should raise_error(Aclatraz::InvalidSuspect)
120
+ end
121
+ end
122
+
123
+ describe "suspect object is symbol" do
124
+ suspects(:foo) {}
125
+
126
+ it "#current_suspect" do
127
+ current_suspect.should == foo
128
+ end
129
+ end
130
+
131
+ describe "suspect object is string" do
132
+ suspects('foo') {}
133
+
134
+ it "#current_suspect" do
135
+ @foo = StubSuspect.new
136
+ current_suspect.should == @foo
137
+ end
138
+ end
139
+
140
+ describe "suspect object includes Suspect class" do
141
+ bar = StubSuspect.new
142
+ suspects(bar) {}
143
+
144
+ it "#current_suspect" do
145
+ current_suspect.should == bar
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aclatraz helpers" do
4
+ include Aclatraz::Helpers
5
+
6
+ it "#camelize should return a camel cased word" do
7
+ camelize("foo_bar_bla").should == "FooBarBla"
8
+ camelize("foo").should == "Foo"
9
+ end
10
+
11
+ it "#pack should return packed permission" do
12
+ pack(10).should == "10"
13
+
14
+ class StubTarget; def id; 10; end; end
15
+ pack(10, StubTarget).should == "10/StubTarget"
16
+
17
+ target = StubTarget.new
18
+ pack(20, target).should == "20/StubTarget/10"
19
+ end
20
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ STORE_SPECS = proc do
4
+ it "should properly assign given roles to owner and check permissions" do
5
+ subject.set("foo", owner)
6
+ subject.check("foo", owner).should be_true
7
+
8
+ subject.set("bar", owner, StubTarget)
9
+ subject.check("bar", owner, StubTarget).should be_true
10
+
11
+ subject.set("bla", owner, target)
12
+ subject.check("bla", owner, target).should be_true
13
+
14
+ subject.check("foo", owner, target).should be_false
15
+ subject.check("foo", owner, StubTarget).should be_false
16
+ subject.check("bar", owner).should be_false
17
+ end
18
+
19
+ it "should properly delete given permission" do
20
+ subject.set("foo", owner)
21
+ subject.set("bar", owner, StubTarget)
22
+ subject.set("bla", owner, target)
23
+
24
+ subject.delete("bar", owner)
25
+ subject.delete("bar", owner, StubTarget)
26
+ subject.delete("bar", owner, target)
27
+
28
+ subject.check("bar", owner).should be_false
29
+ subject.check("bar", owner, StubTarget).should be_false
30
+ subject.check("bar", owner, target).should be_false
31
+ end
32
+
33
+ it "should allow to fetch list of permissions for current role" do
34
+ subject.set("bar", owner)
35
+ subject.set("bar", owner, target)
36
+ class << owner; def id; 20; end; end
37
+ subject.set("bar", owner, StubTarget)
38
+
39
+ (subject.permissions("bar") - ["15", "15/StubTarget/10", "20/StubTarget"]).should be_empty
40
+ subject.permissions("lala").should be_empty
41
+ end
42
+
43
+ it "should allow to fetch whole list of roles" do
44
+ subject.set("foo", owner)
45
+ subject.set("bar", owner)
46
+ subject.set("bla", owner)
47
+
48
+ (subject.roles - ["foo", "bar", "bla"]).should be_empty
49
+ end
50
+
51
+ it "should allow to fetch list of roles for specified member" do
52
+ subject.set("foo", owner)
53
+ subject.set("bar", owner)
54
+ subject.set("bla", owner)
55
+
56
+ (subject.roles(owner.id) - ["foo", "bar", "bla"]).should be_empty
57
+ subject.roles(33).should be_empty
58
+ end
59
+ end
60
+
61
+ describe "Aclatraz" do
62
+ it "should raise InvalidStore error when given store doesn't exists" do
63
+ lambda { Aclatraz.store(:fooobar) }.should raise_error(Aclatraz::InvalidStore)
64
+ end
65
+
66
+ it "should raise StoreNotInitialized error when store has not been set yet" do
67
+ Aclatraz.instance_variable_set('@store', nil)
68
+ lambda { Aclatraz.store }.should raise_error(Aclatraz::StoreNotInitialized)
69
+ end
70
+
71
+ it "should properly set datastore when class given" do
72
+ class TestStore; end
73
+ lambda { Aclatraz.store(TestStore) }.should_not raise_error
74
+ Aclatraz.store.should be_kind_of(TestStore)
75
+ end
76
+
77
+ let(:owner) { StubOwner.new }
78
+ let(:target) { StubTarget.new }
79
+
80
+ describe "Redis store" do
81
+ before(:all) { @redis = Thread.new { `redis-server` } }
82
+ after(:all) { @redis.exit! }
83
+ subject { Aclatraz.store(:redis, "redis://localhost:6379/0") }
84
+ before(:each) { subject.clear }
85
+
86
+ class_eval &STORE_SPECS
87
+ end
88
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aclatraz suspect" do
4
+ before(:all) { Aclatraz.store(:redis, "redis://localhost:6379/0") }
5
+ subject { StubSuspect.new }
6
+ let(:target) { StubTarget.new }
7
+
8
+ its(:acl_suspect?) { should be_true }
9
+
10
+ it "1: should properly set given role" do
11
+ subject.assign_role!(:foobar1)
12
+ subject.assign_role!(:foobar2, StubTarget)
13
+ subject.assign_role!(:foobar3, target)
14
+
15
+ Aclatraz.store.permissions(:foobar1).should include("10")
16
+ Aclatraz.store.permissions(:foobar2).should include("10/StubTarget")
17
+ Aclatraz.store.permissions(:foobar3).should include("10/StubTarget/10")
18
+ end
19
+
20
+ it "2: should properly check given permissions" do
21
+ subject.has_role?(:foobar1).should be_true
22
+ subject.has_role?(:foobar2, StubTarget).should be_true
23
+ subject.has_role?(:foobar3, target).should be_true
24
+ subject.has_role?(:foobar1, StubTarget).should be_false
25
+ end
26
+
27
+ it "3: should allow to get list of roles assigned to user" do
28
+ (subject.roles - ["foobar1", "foobar2", "foobar3"]) .should be_empty
29
+ end
30
+
31
+ it "4: should properly remove given permissions" do
32
+ subject.delete_role!(:foobar1)
33
+ subject.delete_role!(:foobar2, StubTarget)
34
+ subject.delete_role!(:foobar3, target)
35
+
36
+ subject.has_role?(:foobar1).should be_false
37
+ subject.has_role?(:foobar2, StubTarget).should be_false
38
+ subject.has_role?(:foobar3, target).should be_false
39
+ end
40
+
41
+ describe "syntactic sugars" do
42
+ it "1: should properly set given role" do
43
+ subject.is.foobar1!
44
+ subject.is.foobar2_of!(StubTarget)
45
+ subject.is.foobar3_for!(target)
46
+ subject.is.foobar4_on!(target)
47
+ subject.is.foobar5_at!(target)
48
+ subject.is.foobar6_by!(target)
49
+ subject.is.foobar7_in!(target)
50
+
51
+ subject.has_role?(:foobar1).should be_true
52
+ subject.has_role?(:foobar2_of, StubTarget).should be_true
53
+ subject.has_role?(:foobar3_for, target).should be_true
54
+ subject.has_role?(:foobar4_of, target).should be_true
55
+ subject.has_role?(:foobar5_at, target).should be_true
56
+ subject.has_role?(:foobar6_by, target).should be_true
57
+ subject.has_role?(:foobar7_in, target).should be_true
58
+ end
59
+
60
+ it "2: should properly check given permissions" do
61
+ subject.is.foobar1?.should be_true
62
+ subject.is.foobar2_of?(StubTarget).should be_true
63
+ subject.is.foobar3_for?(target).should be_true
64
+ subject.is.foobar4_on?(target).should be_true
65
+ subject.is.foobar5_at?(target).should be_true
66
+ subject.is.foobar6_by?(target).should be_true
67
+ subject.is.foobar7_in?(target).should be_true
68
+ subject.is.foobar8_in?.should be_false
69
+
70
+ subject.is_not.foobar1?.should be_false
71
+ subject.is_not.foobar2_of?(StubTarget).should be_false
72
+ subject.is_not.foobar3_for?(target).should be_false
73
+ subject.is_not.foobar4_on?(target).should be_false
74
+ subject.is_not.foobar5_at?(target).should be_false
75
+ subject.is_not.foobar6_by?(target).should be_false
76
+ subject.is_not.foobar7_in?(target).should be_false
77
+ subject.is_not.foobar8_in?.should be_true
78
+ end
79
+
80
+ it "3: should properly remove given permissions" do
81
+ subject.is_not.foobar1!
82
+ subject.is_not.foobar2_of!(StubTarget)
83
+ subject.is_not.foobar3_for!(target)
84
+ subject.is_not.foobar4_on!(target)
85
+ subject.is_not.foobar5_at!(target)
86
+ subject.is_not.foobar6_by!(target)
87
+ subject.is_not.foobar7_in!(target)
88
+
89
+ subject.is.foobar1?.should be_false
90
+ subject.is.foobar2_of?(StubTarget).should be_false
91
+ subject.is.foobar3_for?(target).should be_false
92
+ subject.is.foobar4_on?(target).should be_false
93
+ subject.is.foobar5_at?(target).should be_false
94
+ subject.is.foobar6_by?(target).should be_false
95
+ subject.is.foobar7_in?(target).should be_false
96
+ end
97
+
98
+ it "4: should raise NoMethodError when there is not checker or setter/deleter called" do
99
+ lambda { subject.is.foobar }.should raise_error(NoMethodError)
100
+ lambda { subject.is_not.foobar }.should raise_error(NoMethodError)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aclatraz" do
4
+
5
+ end
6
+
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --backtrace
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ $VERBOSE = nil
5
+
6
+ require 'rubygems'
7
+ require 'aclatraz'
8
+ require 'spec'
9
+ require 'spec/autorun'
10
+
11
+ Spec::Runner.configure do |config|
12
+ config.mock_with :mocha
13
+ end
14
+
15
+ class StubSuspect
16
+ include Aclatraz::Suspect
17
+ def id; 10; end
18
+ end
19
+
20
+ class StubTarget
21
+ def id; 10; end
22
+ end
23
+
24
+ class StubOwner
25
+ def id; 15; end
26
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aclatraz
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Kriss 'nu7hatch' Kowalik
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-10 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 25
46
+ segments:
47
+ - 0
48
+ - 9
49
+ version: "0.9"
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: redis
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 2
63
+ - 0
64
+ version: "2.0"
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: dictionary
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 15
76
+ segments:
77
+ - 1
78
+ - 0
79
+ version: "1.0"
80
+ type: :runtime
81
+ version_requirements: *id004
82
+ description: Extremaly fast and flexible access control mechanism inspired by *nix ACLs, powered by fast key value stores like Redis or TokyoCabinet.
83
+ email: kriss.kowalik@gmail.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files:
89
+ - LICENSE
90
+ - README.rdoc
91
+ files:
92
+ - .document
93
+ - .gitignore
94
+ - LICENSE
95
+ - README.rdoc
96
+ - Rakefile
97
+ - VERSION
98
+ - lib/aclatraz.rb
99
+ - lib/aclatraz/acl.rb
100
+ - lib/aclatraz/guard.rb
101
+ - lib/aclatraz/helpers.rb
102
+ - lib/aclatraz/store.rb
103
+ - lib/aclatraz/store/redis.rb
104
+ - lib/aclatraz/suspect.rb
105
+ - spec/aclatraz/acl_spec.rb
106
+ - spec/aclatraz/guard_spec.rb
107
+ - spec/aclatraz/helpers_spec.rb
108
+ - spec/aclatraz/stores_spec.rb
109
+ - spec/aclatraz/suspect_spec.rb
110
+ - spec/aclatraz_spec.rb
111
+ - spec/spec.opts
112
+ - spec/spec_helper.rb
113
+ has_rdoc: true
114
+ homepage: http://github.com/nu7hatch/aclatraz
115
+ licenses: []
116
+
117
+ post_install_message:
118
+ rdoc_options:
119
+ - --charset=UTF-8
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 3
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ requirements: []
141
+
142
+ rubyforge_project:
143
+ rubygems_version: 1.3.7
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: Flexible access control that doesn't sucks!
147
+ test_files:
148
+ - spec/spec_helper.rb
149
+ - spec/aclatraz/guard_spec.rb
150
+ - spec/aclatraz/helpers_spec.rb
151
+ - spec/aclatraz/acl_spec.rb
152
+ - spec/aclatraz/stores_spec.rb
153
+ - spec/aclatraz/suspect_spec.rb
154
+ - spec/aclatraz_spec.rb