aclatraz 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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