consul 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of consul might be problematic. Click here for more details.
- data/.gitignore +7 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +58 -0
- data/README.rdoc +213 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/consul.gemspec +93 -0
- data/lib/consul.rb +3 -0
- data/lib/consul/controller.rb +79 -0
- data/lib/consul/errors.rb +5 -0
- data/lib/consul/power.rb +52 -0
- data/lib/consul/spec/matchers.rb +40 -0
- data/spec/app_root/app/controllers/application_controller.rb +3 -0
- data/spec/app_root/app/models/client.rb +13 -0
- data/spec/app_root/app/models/note.rb +7 -0
- data/spec/app_root/app/models/power.rb +28 -0
- data/spec/app_root/app/models/user.rb +8 -0
- data/spec/app_root/config/boot.rb +114 -0
- data/spec/app_root/config/database.yml +21 -0
- data/spec/app_root/config/environment.rb +14 -0
- data/spec/app_root/config/environments/in_memory.rb +0 -0
- data/spec/app_root/config/environments/mysql.rb +0 -0
- data/spec/app_root/config/environments/postgresql.rb +0 -0
- data/spec/app_root/config/environments/sqlite.rb +0 -0
- data/spec/app_root/config/environments/sqlite3.rb +0 -0
- data/spec/app_root/config/routes.rb +7 -0
- data/spec/app_root/db/migrate/001_create_users.rb +11 -0
- data/spec/app_root/db/migrate/002_create_clients.rb +13 -0
- data/spec/app_root/db/migrate/003_create_notes.rb +13 -0
- data/spec/app_root/lib/console_with_fixtures.rb +4 -0
- data/spec/app_root/script/console +7 -0
- data/spec/consul/power_spec.rb +112 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +20 -0
- metadata +120 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
consul (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
actionmailer (2.3.10)
|
10
|
+
actionpack (= 2.3.10)
|
11
|
+
actionpack (2.3.10)
|
12
|
+
activesupport (= 2.3.10)
|
13
|
+
rack (~> 1.1.0)
|
14
|
+
activerecord (2.3.10)
|
15
|
+
activesupport (= 2.3.10)
|
16
|
+
activeresource (2.3.10)
|
17
|
+
activesupport (= 2.3.10)
|
18
|
+
activesupport (2.3.10)
|
19
|
+
columnize (0.3.2)
|
20
|
+
gemcutter (0.6.1)
|
21
|
+
git (1.2.5)
|
22
|
+
jeweler (1.4.0)
|
23
|
+
gemcutter (>= 0.1.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
rubyforge (>= 2.0.0)
|
26
|
+
json_pure (1.4.6)
|
27
|
+
linecache (0.43)
|
28
|
+
rack (1.1.0)
|
29
|
+
rails (2.3.10)
|
30
|
+
actionmailer (= 2.3.10)
|
31
|
+
actionpack (= 2.3.10)
|
32
|
+
activerecord (= 2.3.10)
|
33
|
+
activeresource (= 2.3.10)
|
34
|
+
activesupport (= 2.3.10)
|
35
|
+
rake (>= 0.8.3)
|
36
|
+
rake (0.8.7)
|
37
|
+
rspec (1.3.1)
|
38
|
+
rspec-rails (1.3.3)
|
39
|
+
rack (>= 1.0.0)
|
40
|
+
rspec (= 1.3.1)
|
41
|
+
ruby-debug (0.10.4)
|
42
|
+
columnize (>= 0.1)
|
43
|
+
ruby-debug-base (~> 0.10.4.0)
|
44
|
+
ruby-debug-base (0.10.4)
|
45
|
+
linecache (>= 0.3)
|
46
|
+
rubyforge (2.0.4)
|
47
|
+
json_pure (>= 1.1.7)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
consul!
|
54
|
+
jeweler
|
55
|
+
rails (< 3.0.0)
|
56
|
+
rspec (= 1.3.1)
|
57
|
+
rspec-rails (= 1.3.3)
|
58
|
+
ruby-debug
|
data/README.rdoc
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
= Consul - A scope-based authorization solution
|
2
|
+
|
3
|
+
Consul is a authorization solution for Ruby on Rails that uses scopes to control what a user can see or edit.
|
4
|
+
|
5
|
+
|
6
|
+
== Status of this project
|
7
|
+
|
8
|
+
Consul is a new kind of authorization solution for Rails applications that are mainly driven by scopes.
|
9
|
+
While Consul has been used in production code, we are still figuring out whether or not it is a good idea.
|
10
|
+
Also documentation is still sparse.
|
11
|
+
|
12
|
+
If you are looking for something less adventurous with a stable API and great documentation, checkout out our
|
13
|
+
other authorization solution, {Aegis}[https://github.com/makandra/aegis].
|
14
|
+
|
15
|
+
|
16
|
+
== Describing a power for your application
|
17
|
+
|
18
|
+
You describe access to your application by putting a <tt>Power</tt> model into <tt>app/models/power.rb</tt>:
|
19
|
+
|
20
|
+
class Power
|
21
|
+
include Consul::Power
|
22
|
+
|
23
|
+
def initialize(user)
|
24
|
+
@user = user
|
25
|
+
end
|
26
|
+
|
27
|
+
power :notes do
|
28
|
+
Note.by_author(@user)
|
29
|
+
end
|
30
|
+
|
31
|
+
power :users do
|
32
|
+
User if @user.admin?
|
33
|
+
end
|
34
|
+
|
35
|
+
power :dashboard do
|
36
|
+
true # not a scope, but a boolean power. This is useful to control access to stuff that doesn't live in the database.
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
== Querying a power
|
43
|
+
|
44
|
+
Common things you might want from a power:
|
45
|
+
|
46
|
+
1. Get its scope
|
47
|
+
2. Ask whether it is there
|
48
|
+
3. Raise an error unless it its there
|
49
|
+
4. Ask whether a given record is included in its scope
|
50
|
+
5. Raise an error unless a given record is included in its scope
|
51
|
+
|
52
|
+
Here is how to do all of that:
|
53
|
+
|
54
|
+
power = Power.new(user)
|
55
|
+
power.notes # => returns an ActiveRecord::Scope
|
56
|
+
power.notes? # => returns true if Power#notes returns a scope
|
57
|
+
power.notes! # => raises Consul::Powerless unless Power#notes returns a scope
|
58
|
+
power.note?(Note.last) # => returns whether the given Note is in the Power#notes scope. Caches the result for subsequent queries.
|
59
|
+
power.note!(Note.last) # => raises Consul::Powerless unless the given Note is in the Power#notes scope
|
60
|
+
|
61
|
+
You can also write power checks like this:
|
62
|
+
|
63
|
+
power.include?(:notes)
|
64
|
+
power.include!(:notes)
|
65
|
+
power.include?(:note, Note.last)
|
66
|
+
power.include!(:note, Note.last)
|
67
|
+
|
68
|
+
|
69
|
+
== Boolean powers
|
70
|
+
|
71
|
+
Boolean powers are useful to control access to stuff that doesn't live in the database:
|
72
|
+
|
73
|
+
class Power
|
74
|
+
...
|
75
|
+
|
76
|
+
power :dashboard do
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
You can query it like the other powers:
|
83
|
+
|
84
|
+
power.dashboard? # => true
|
85
|
+
power.dashboard! # => raises Consul::Powerless unless Power#dashboard? returns true
|
86
|
+
|
87
|
+
|
88
|
+
== Role-based permissions
|
89
|
+
|
90
|
+
Consul has no built-in support for role-based permissions, but you can easily implement it yourself. Let's say your <tt>User</tt> model has a string column <tt>role</tt> which can be <tt>"author"</tt> or "<tt>"admin"</tt>:
|
91
|
+
|
92
|
+
class Power
|
93
|
+
include Consul::Power
|
94
|
+
|
95
|
+
def initialize(user)
|
96
|
+
@user = user
|
97
|
+
end
|
98
|
+
|
99
|
+
power :notes do
|
100
|
+
case role
|
101
|
+
when :admin then Note
|
102
|
+
when :author then Note.by_author
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def role
|
109
|
+
@user.role.to_sym
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
== Controller integration
|
116
|
+
|
117
|
+
It is convenient to expose a helper method <tt>current_power</tt> for your controllers and views:
|
118
|
+
|
119
|
+
class ApplicationController < ActionController::Base
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def current_power
|
124
|
+
@current_power ||= Power.new(current_user)
|
125
|
+
end
|
126
|
+
|
127
|
+
helper_method :current_power
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
You can now use power scopes to control access:
|
132
|
+
|
133
|
+
class NotesController < ApplicationController
|
134
|
+
|
135
|
+
def show
|
136
|
+
@note = current_power.notes.find(params[:id])
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
Get convenient controller macros by including <tt>Consul::Controller</tt> in your application:
|
142
|
+
|
143
|
+
class ApplicationController < ActionController::Base
|
144
|
+
include Consul::Controller
|
145
|
+
end
|
146
|
+
|
147
|
+
To make sure a power is given before every action in a controller:
|
148
|
+
|
149
|
+
class NotesController < ApplicationController
|
150
|
+
power :notes
|
151
|
+
end
|
152
|
+
|
153
|
+
You can use <tt>:except</tt> and <tt>:only</tt> options like in before filters.
|
154
|
+
|
155
|
+
You can also map different powers to different actions:
|
156
|
+
|
157
|
+
class NotesController < ApplicationController
|
158
|
+
power :notes, :map => { [:edit, :update, :destroy] => :changable_notes }
|
159
|
+
end
|
160
|
+
|
161
|
+
It is often convenient to map a power scope to a private controller method:
|
162
|
+
|
163
|
+
class NotesController < ApplicationController
|
164
|
+
|
165
|
+
power :notes, :as => end_of_association_chain
|
166
|
+
|
167
|
+
def show
|
168
|
+
@note = end_of_association_chain.find(params[:id])
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
This is especially useful when you are using a RESTful controller library like {resource_controller}[https://github.com/jamesgolick/resource_controller]. The mapped method is aware of the <tt>:map</tt> option.
|
174
|
+
|
175
|
+
You can force yourself to use a <tt>power</tt> check in every controller. This will raise <tt>Consul::UncheckedPower</tt> if you ever forget it:
|
176
|
+
|
177
|
+
class ApplicationController < ActionController::Base
|
178
|
+
include Consul::Controller
|
179
|
+
require_power_check
|
180
|
+
end
|
181
|
+
|
182
|
+
Should you for some obscure reason want to forego the power check:
|
183
|
+
|
184
|
+
class ApiController < ApplicationController
|
185
|
+
skip_power_check
|
186
|
+
end
|
187
|
+
|
188
|
+
== Installation
|
189
|
+
|
190
|
+
Add the following to your <tt>Gemfile</tt>:
|
191
|
+
gem 'consul'
|
192
|
+
|
193
|
+
Now run
|
194
|
+
bundle install
|
195
|
+
|
196
|
+
|
197
|
+
== Rails 3 compatibility
|
198
|
+
|
199
|
+
We cannot guarantee Rails 3 compatibility at this point.
|
200
|
+
|
201
|
+
|
202
|
+
== Development
|
203
|
+
|
204
|
+
A Rails 2 test application lives in <tt>spec/app_root</tt>. You can run specs from the project root by saying:
|
205
|
+
|
206
|
+
bundle exec rake spec
|
207
|
+
|
208
|
+
|
209
|
+
== Credits
|
210
|
+
|
211
|
+
Henning Koch
|
212
|
+
|
213
|
+
{makandra.com}[http://makandra.com/]
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
Spec::Rake::SpecTask.new() do |t|
|
8
|
+
t.spec_opts = ['--options', "\"spec/spec.opts\""]
|
9
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Generate documentation for the consul gem'
|
13
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
14
|
+
rdoc.rdoc_dir = 'rdoc'
|
15
|
+
rdoc.title = 'consul'
|
16
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
17
|
+
rdoc.rdoc_files.include('README')
|
18
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require 'jeweler'
|
23
|
+
Jeweler::Tasks.new do |gemspec|
|
24
|
+
gemspec.name = "consul"
|
25
|
+
gemspec.summary = "Scope-based authorization solution for Rails"
|
26
|
+
gemspec.email = "henning.koch@makandra.de"
|
27
|
+
gemspec.homepage = "http://github.com/makandra/consul"
|
28
|
+
gemspec.description = "Consul is a scope-based authorization solution for Ruby on Rails."
|
29
|
+
gemspec.authors = ["Henning Koch"]
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
33
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/consul.gemspec
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{consul}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Henning Koch"]
|
12
|
+
s.date = %q{2011-04-12}
|
13
|
+
s.description = %q{Consul is a scope-based authorization solution for Ruby on Rails.}
|
14
|
+
s.email = %q{henning.koch@makandra.de}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"consul.gemspec",
|
26
|
+
"lib/consul.rb",
|
27
|
+
"lib/consul/controller.rb",
|
28
|
+
"lib/consul/errors.rb",
|
29
|
+
"lib/consul/power.rb",
|
30
|
+
"lib/consul/spec/matchers.rb",
|
31
|
+
"spec/app_root/app/controllers/application_controller.rb",
|
32
|
+
"spec/app_root/app/models/client.rb",
|
33
|
+
"spec/app_root/app/models/note.rb",
|
34
|
+
"spec/app_root/app/models/power.rb",
|
35
|
+
"spec/app_root/app/models/user.rb",
|
36
|
+
"spec/app_root/config/boot.rb",
|
37
|
+
"spec/app_root/config/database.yml",
|
38
|
+
"spec/app_root/config/environment.rb",
|
39
|
+
"spec/app_root/config/environments/in_memory.rb",
|
40
|
+
"spec/app_root/config/environments/mysql.rb",
|
41
|
+
"spec/app_root/config/environments/postgresql.rb",
|
42
|
+
"spec/app_root/config/environments/sqlite.rb",
|
43
|
+
"spec/app_root/config/environments/sqlite3.rb",
|
44
|
+
"spec/app_root/config/routes.rb",
|
45
|
+
"spec/app_root/db/migrate/001_create_users.rb",
|
46
|
+
"spec/app_root/db/migrate/002_create_clients.rb",
|
47
|
+
"spec/app_root/db/migrate/003_create_notes.rb",
|
48
|
+
"spec/app_root/lib/console_with_fixtures.rb",
|
49
|
+
"spec/app_root/log/.gitignore",
|
50
|
+
"spec/app_root/script/console",
|
51
|
+
"spec/consul/power_spec.rb",
|
52
|
+
"spec/rcov.opts",
|
53
|
+
"spec/spec.opts",
|
54
|
+
"spec/spec_helper.rb"
|
55
|
+
]
|
56
|
+
s.homepage = %q{http://github.com/makandra/consul}
|
57
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
58
|
+
s.require_paths = ["lib"]
|
59
|
+
s.rubygems_version = %q{1.3.7}
|
60
|
+
s.summary = %q{Scope-based authorization solution for Rails}
|
61
|
+
s.test_files = [
|
62
|
+
"spec/app_root/db/migrate/001_create_users.rb",
|
63
|
+
"spec/app_root/db/migrate/002_create_clients.rb",
|
64
|
+
"spec/app_root/db/migrate/003_create_notes.rb",
|
65
|
+
"spec/app_root/config/boot.rb",
|
66
|
+
"spec/app_root/config/environment.rb",
|
67
|
+
"spec/app_root/config/routes.rb",
|
68
|
+
"spec/app_root/config/environments/in_memory.rb",
|
69
|
+
"spec/app_root/config/environments/mysql.rb",
|
70
|
+
"spec/app_root/config/environments/postgresql.rb",
|
71
|
+
"spec/app_root/config/environments/sqlite.rb",
|
72
|
+
"spec/app_root/config/environments/sqlite3.rb",
|
73
|
+
"spec/app_root/lib/console_with_fixtures.rb",
|
74
|
+
"spec/app_root/app/controllers/application_controller.rb",
|
75
|
+
"spec/app_root/app/models/client.rb",
|
76
|
+
"spec/app_root/app/models/note.rb",
|
77
|
+
"spec/app_root/app/models/power.rb",
|
78
|
+
"spec/app_root/app/models/user.rb",
|
79
|
+
"spec/consul/power_spec.rb",
|
80
|
+
"spec/spec_helper.rb"
|
81
|
+
]
|
82
|
+
|
83
|
+
if s.respond_to? :specification_version then
|
84
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
85
|
+
s.specification_version = 3
|
86
|
+
|
87
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
88
|
+
else
|
89
|
+
end
|
90
|
+
else
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
data/lib/consul.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Consul
|
2
|
+
module Controller
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def require_power_check(options = {})
|
14
|
+
before_filter :unchecked_power, options
|
15
|
+
end
|
16
|
+
|
17
|
+
def skip_power_check(options = {})
|
18
|
+
skip_before_filter :unchecked_power, options
|
19
|
+
end
|
20
|
+
|
21
|
+
def power(*args)
|
22
|
+
|
23
|
+
args_copy = args.dup
|
24
|
+
options = args_copy.extract_options!
|
25
|
+
default_power = args_copy.shift # might be nil
|
26
|
+
|
27
|
+
filter_options = options.slice(:except, :only)
|
28
|
+
skip_power_check filter_options
|
29
|
+
|
30
|
+
power_method = options[:power] || :current_power
|
31
|
+
actions_map = (options[:map] || {})
|
32
|
+
|
33
|
+
direct_access_method = options[:as]
|
34
|
+
|
35
|
+
# Store arguments for testing
|
36
|
+
@consul_power_args = args
|
37
|
+
|
38
|
+
before_filter :check_power, filter_options
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
define_method :check_power do
|
43
|
+
send(power_method).include!(power_for_action)
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method direct_access_method do
|
47
|
+
send(power_method).send(power_for_action)
|
48
|
+
end if direct_access_method
|
49
|
+
|
50
|
+
define_method :power_for_action do
|
51
|
+
key = actions_map.keys.detect do |actions|
|
52
|
+
Array(actions).collect(&:to_s).include?(action_name)
|
53
|
+
end
|
54
|
+
if key
|
55
|
+
actions_map[key]
|
56
|
+
elsif default_power
|
57
|
+
default_power
|
58
|
+
else
|
59
|
+
raise Consul::UnmappedAction, "Could not map the action ##{action_name} to a power"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
module InstanceMethods
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def unchecked_permissions
|
72
|
+
raise Consul::UncheckedPower, "This controller does not check against a power"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/consul/power.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Consul
|
2
|
+
module Power
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ActiveSupport::Memoizable
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
def include?(name, *args)
|
10
|
+
args = args.dup
|
11
|
+
record = args.shift
|
12
|
+
power_value = send(name)
|
13
|
+
if record.nil? || boolean_or_nil?(power_value)
|
14
|
+
!!power_value
|
15
|
+
else
|
16
|
+
power_ids_name = self.class.power_ids_name(name)
|
17
|
+
send(power_ids_name, *args).include?(record.id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def include!(*args)
|
22
|
+
include?(*args) or raise Consul::Powerless.new("No power to #{args.inspect}")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def boolean_or_nil?(value)
|
28
|
+
[TrueClass, FalseClass, NilClass].include?(value.class)
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
|
33
|
+
def power(name, &block)
|
34
|
+
define_method(name, &block)
|
35
|
+
define_method("#{name.to_s}?") { |*args| include?(name, *args) }
|
36
|
+
define_method("#{name.to_s}!") { |*args| include!(name, *args) }
|
37
|
+
define_method("#{name.to_s.singularize}?") { |*args| include?(name, *args) }
|
38
|
+
define_method("#{name.to_s.singularize}!") { |*args| include!(name, *args) }
|
39
|
+
ids_method = power_ids_name(name)
|
40
|
+
define_method(ids_method) { |*args| send(name, *args).scoped(:select => 'id').collect(&:id) }
|
41
|
+
memoize ids_method
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
45
|
+
def power_ids_name(name)
|
46
|
+
"#{name.to_s.singularize}_ids"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Aegis
|
2
|
+
module Spec
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
class CheckPower
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
@expected_args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(controller)
|
12
|
+
@controller_class = controller.class
|
13
|
+
@actual_args = @controller_class.instance_variable_get('@consul_power_args')
|
14
|
+
@actual_args == @expected_args
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message
|
18
|
+
"expected #{@controller_class} to check against power #{@expected_args.inspect} but it checked against #{@actual_args.inspect}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def negative_failure_message
|
22
|
+
"expected #{@controller_class} to not check against power #{@expected_args.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
description = "check against power #{@expected_args.inspect}"
|
27
|
+
description
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_power(*args)
|
33
|
+
CheckPower.new(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
ActiveSupport::TestCase.send :include, Aegis::Spec::Matchers
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Power
|
2
|
+
include Consul::Power
|
3
|
+
|
4
|
+
def initialize(user)
|
5
|
+
@user = user
|
6
|
+
end
|
7
|
+
|
8
|
+
power :clients do
|
9
|
+
Client.active
|
10
|
+
end
|
11
|
+
|
12
|
+
power :client_notes do |client|
|
13
|
+
client.notes
|
14
|
+
end
|
15
|
+
|
16
|
+
power :admin do
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
power :moderator do
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
power :dashboard do
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Allow customization of the rails framework path
|
2
|
+
RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT)
|
3
|
+
|
4
|
+
# Don't change this file!
|
5
|
+
# Configure your app in config/environment.rb and config/environments/*.rb
|
6
|
+
|
7
|
+
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
class << self
|
11
|
+
def boot!
|
12
|
+
unless booted?
|
13
|
+
preinitialize
|
14
|
+
pick_boot.run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def booted?
|
19
|
+
defined? Rails::Initializer
|
20
|
+
end
|
21
|
+
|
22
|
+
def pick_boot
|
23
|
+
(vendor_rails? ? VendorBoot : GemBoot).new
|
24
|
+
end
|
25
|
+
|
26
|
+
def vendor_rails?
|
27
|
+
File.exist?(RAILS_FRAMEWORK_ROOT)
|
28
|
+
end
|
29
|
+
|
30
|
+
def preinitialize
|
31
|
+
load(preinitializer_path) if File.exist?(preinitializer_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def preinitializer_path
|
35
|
+
"#{RAILS_ROOT}/config/preinitializer.rb"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Boot
|
40
|
+
def run
|
41
|
+
load_initializer
|
42
|
+
Rails::Initializer.run(:set_load_path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class VendorBoot < Boot
|
47
|
+
def load_initializer
|
48
|
+
require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer"
|
49
|
+
Rails::Initializer.run(:install_gem_spec_stubs)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class GemBoot < Boot
|
54
|
+
def load_initializer
|
55
|
+
self.class.load_rubygems
|
56
|
+
load_rails_gem
|
57
|
+
require 'initializer'
|
58
|
+
end
|
59
|
+
|
60
|
+
def load_rails_gem
|
61
|
+
if version = self.class.gem_version
|
62
|
+
gem 'rails', version
|
63
|
+
else
|
64
|
+
gem 'rails'
|
65
|
+
end
|
66
|
+
rescue Gem::LoadError => load_error
|
67
|
+
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
def rubygems_version
|
73
|
+
Gem::RubyGemsVersion rescue nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def gem_version
|
77
|
+
if defined? RAILS_GEM_VERSION
|
78
|
+
RAILS_GEM_VERSION
|
79
|
+
elsif ENV.include?('RAILS_GEM_VERSION')
|
80
|
+
ENV['RAILS_GEM_VERSION']
|
81
|
+
else
|
82
|
+
parse_gem_version(read_environment_rb)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_rubygems
|
87
|
+
require 'rubygems'
|
88
|
+
min_version = '1.1.1'
|
89
|
+
unless rubygems_version >= min_version
|
90
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
|
94
|
+
rescue LoadError
|
95
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_gem_version(text)
|
100
|
+
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def read_environment_rb
|
105
|
+
environment_rb = "#{RAILS_ROOT}/config/environment.rb"
|
106
|
+
environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb)
|
107
|
+
File.read(environment_rb)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# All that for this:
|
114
|
+
Rails.boot!
|
@@ -0,0 +1,21 @@
|
|
1
|
+
in_memory:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ":memory:"
|
4
|
+
verbosity: quiet
|
5
|
+
sqlite:
|
6
|
+
adapter: sqlite
|
7
|
+
dbfile: plugin_test.sqlite.db
|
8
|
+
sqlite3:
|
9
|
+
adapter: sqlite3
|
10
|
+
dbfile: plugin_test.sqlite3.db
|
11
|
+
postgresql:
|
12
|
+
adapter: postgresql
|
13
|
+
username: postgres
|
14
|
+
password: postgres
|
15
|
+
database: plugin_test
|
16
|
+
mysql:
|
17
|
+
adapter: mysql
|
18
|
+
host: localhost
|
19
|
+
username: root
|
20
|
+
password:
|
21
|
+
database: plugin_test
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
2
|
+
|
3
|
+
Rails::Initializer.run do |config|
|
4
|
+
config.cache_classes = false
|
5
|
+
config.whiny_nils = true
|
6
|
+
config.action_controller.session = { :key => "_myapp_session", :secret => "gwirofjweroijger8924rt2zfwehfuiwehb1378rifowenfoqwphf23" }
|
7
|
+
config.plugin_locators.unshift(
|
8
|
+
Class.new(Rails::Plugin::Locator) do
|
9
|
+
def plugins
|
10
|
+
[Rails::Plugin.new(File.expand_path('.'))]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
) unless defined?(PluginTestHelper::PluginLocator)
|
14
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,4 @@
|
|
1
|
+
# Loads fixtures into the database when running the test app via the console
|
2
|
+
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(Rails.root, '../fixtures/*.{yml,csv}'))).each do |fixture_file|
|
3
|
+
Fixtures.create_fixtures(File.join(Rails.root, '../fixtures'), File.basename(fixture_file, '.*'))
|
4
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Consul::Power do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@user = User.create!
|
7
|
+
@deleted_client = Client.create!(:deleted => true)
|
8
|
+
@client1 = Client.create!
|
9
|
+
@client1_note1 = @client1.notes.create!
|
10
|
+
@client1_note2 = @client1.notes.create!
|
11
|
+
@client2 = Client.create!
|
12
|
+
@client2_note1 = @client2.notes.create!
|
13
|
+
@client2_note2 = @client2.notes.create!
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'example scenario' do
|
17
|
+
|
18
|
+
it 'should work as expected' do
|
19
|
+
Client.active.should == [@client1, @client2]
|
20
|
+
@client1.notes.should == [@client1_note1, @client1_note2]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'scope methods' do
|
26
|
+
|
27
|
+
it 'should return the registered scope' do
|
28
|
+
@user.power.clients.all.should == [@client1, @client2]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should allow to register scopes with arguments' do
|
32
|
+
@user.power.client_notes(@client1).should == [@client1_note1, @client1_note2]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'scope_ids methods' do
|
38
|
+
|
39
|
+
it 'should return record ids that match the registered scope' do
|
40
|
+
@user.power.client_ids.should == [@client1.id, @client2.id]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should cache scope ids' do
|
44
|
+
@user.power.should_receive(:clients).once.and_return(double('scope').as_null_object)
|
45
|
+
2.times { @user.power.client_ids }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'include?' do
|
51
|
+
|
52
|
+
it 'should return true if a given record belongs to a scope' do
|
53
|
+
@user.power.client?(@client1).should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should return false if a given record does not belong to a scope' do
|
57
|
+
@user.power.client?(@deleted_client).should be_false
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should only trigger a single query for multiple checks on the same scope' do
|
61
|
+
ActiveRecord::Base.connection.should_receive(:select_all).once.and_return([]) #.and_return(double('connection').as_null_object)
|
62
|
+
@user.power.client?(@client1)
|
63
|
+
@user.power.client?(@deleted_client)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return true when the queried power returns a scope (which might or might not match records)' do
|
67
|
+
@user.power.clients?.should be_true
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should return true when the queried power is not a scope, but returns true' do
|
71
|
+
@user.power.dashboard?.should be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should return false when the queried power is not a scope, but returns false' do
|
75
|
+
@user.power.admin?.should be_false
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should return false when the queried power is not a scope, but returns nil' do
|
79
|
+
@user.power.moderator?.should be_false
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'include!' do
|
85
|
+
|
86
|
+
it 'should raise Consul::Powerless when the given record belongs to a scope' do
|
87
|
+
expect { @user.power.client!(@deleted_client) }.to raise_error(Consul::Powerless)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should not raise Consul::Powerless when the given record does not belong to a scope' do
|
91
|
+
expect { @user.power.client!(@client1) }.to_not raise_error
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should not raise Consul::Powerless when the queried power returns a scope (which might or might not match records)' do
|
95
|
+
expect { @user.power.clients! }.to_not raise_error
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not raise Consul::Powerless when the queried power is not a scope, but returns true' do
|
99
|
+
expect { @user.power.dashboard! }.to_not raise_error
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should raise Consul::Powerless when the queried power is not a scope, but returns false' do
|
103
|
+
expect { @user.power.admin! }.to raise_error
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should raise Consul::Powerless when the queried power is not a scope, but returns nil' do
|
107
|
+
expect { @user.power.moderator! }.to raise_error
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "/../lib" )
|
2
|
+
|
3
|
+
# Set the default environment to sqlite3's in_memory database
|
4
|
+
ENV['RAILS_ENV'] ||= 'in_memory'
|
5
|
+
|
6
|
+
# Load the Rails environment and testing framework
|
7
|
+
require "#{File.dirname(__FILE__)}/app_root/config/environment"
|
8
|
+
require "#{File.dirname(__FILE__)}/../lib/consul"
|
9
|
+
require 'spec/rails'
|
10
|
+
|
11
|
+
# Undo changes to RAILS_ENV
|
12
|
+
silence_warnings {RAILS_ENV = ENV['RAILS_ENV']}
|
13
|
+
|
14
|
+
# Run the migrations
|
15
|
+
ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
|
16
|
+
|
17
|
+
Spec::Runner.configure do |config|
|
18
|
+
config.use_transactional_fixtures = true
|
19
|
+
config.use_instantiated_fixtures = false
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: consul
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Henning Koch
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-12 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Consul is a scope-based authorization solution for Ruby on Rails.
|
23
|
+
email: henning.koch@makandra.de
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- Gemfile.lock
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- consul.gemspec
|
38
|
+
- lib/consul.rb
|
39
|
+
- lib/consul/controller.rb
|
40
|
+
- lib/consul/errors.rb
|
41
|
+
- lib/consul/power.rb
|
42
|
+
- lib/consul/spec/matchers.rb
|
43
|
+
- spec/app_root/app/controllers/application_controller.rb
|
44
|
+
- spec/app_root/app/models/client.rb
|
45
|
+
- spec/app_root/app/models/note.rb
|
46
|
+
- spec/app_root/app/models/power.rb
|
47
|
+
- spec/app_root/app/models/user.rb
|
48
|
+
- spec/app_root/config/boot.rb
|
49
|
+
- spec/app_root/config/database.yml
|
50
|
+
- spec/app_root/config/environment.rb
|
51
|
+
- spec/app_root/config/environments/in_memory.rb
|
52
|
+
- spec/app_root/config/environments/mysql.rb
|
53
|
+
- spec/app_root/config/environments/postgresql.rb
|
54
|
+
- spec/app_root/config/environments/sqlite.rb
|
55
|
+
- spec/app_root/config/environments/sqlite3.rb
|
56
|
+
- spec/app_root/config/routes.rb
|
57
|
+
- spec/app_root/db/migrate/001_create_users.rb
|
58
|
+
- spec/app_root/db/migrate/002_create_clients.rb
|
59
|
+
- spec/app_root/db/migrate/003_create_notes.rb
|
60
|
+
- spec/app_root/lib/console_with_fixtures.rb
|
61
|
+
- spec/app_root/log/.gitignore
|
62
|
+
- spec/app_root/script/console
|
63
|
+
- spec/consul/power_spec.rb
|
64
|
+
- spec/rcov.opts
|
65
|
+
- spec/spec.opts
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://github.com/makandra/consul
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options:
|
73
|
+
- --charset=UTF-8
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.7
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Scope-based authorization solution for Rails
|
101
|
+
test_files:
|
102
|
+
- spec/app_root/db/migrate/001_create_users.rb
|
103
|
+
- spec/app_root/db/migrate/002_create_clients.rb
|
104
|
+
- spec/app_root/db/migrate/003_create_notes.rb
|
105
|
+
- spec/app_root/config/boot.rb
|
106
|
+
- spec/app_root/config/environment.rb
|
107
|
+
- spec/app_root/config/routes.rb
|
108
|
+
- spec/app_root/config/environments/in_memory.rb
|
109
|
+
- spec/app_root/config/environments/mysql.rb
|
110
|
+
- spec/app_root/config/environments/postgresql.rb
|
111
|
+
- spec/app_root/config/environments/sqlite.rb
|
112
|
+
- spec/app_root/config/environments/sqlite3.rb
|
113
|
+
- spec/app_root/lib/console_with_fixtures.rb
|
114
|
+
- spec/app_root/app/controllers/application_controller.rb
|
115
|
+
- spec/app_root/app/models/client.rb
|
116
|
+
- spec/app_root/app/models/note.rb
|
117
|
+
- spec/app_root/app/models/power.rb
|
118
|
+
- spec/app_root/app/models/user.rb
|
119
|
+
- spec/consul/power_spec.rb
|
120
|
+
- spec/spec_helper.rb
|