helioth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +22 -0
- data/Rakefile +28 -0
- data/lib/helioth.rb +25 -0
- data/lib/helioth/action.rb +23 -0
- data/lib/helioth/controller_additions.rb +39 -0
- data/lib/helioth/controller_resource.rb +34 -0
- data/lib/helioth/dsl.rb +116 -0
- data/lib/helioth/feature.rb +40 -0
- data/lib/helioth/features.rb +15 -0
- data/lib/helioth/model_additions.rb +49 -0
- data/lib/helioth/relation.rb +27 -0
- data/lib/helioth/role.rb +19 -0
- data/lib/helioth/version.rb +3 -0
- data/spec/controller_additions_spec.rb +116 -0
- data/spec/dsl_spec.rb +142 -0
- data/spec/feature_spec.rb +49 -0
- data/spec/features_spec.rb +37 -0
- data/spec/fixtures/invalid_dsl.rb +3 -0
- data/spec/fixtures/valid_dsl.rb +48 -0
- data/spec/helioth_spec.rb +19 -0
- data/spec/model_additions_spec.rb +146 -0
- data/spec/relation_spec.rb +56 -0
- data/spec/role_spec.rb +31 -0
- data/spec/schema.rb +25 -0
- data/spec/spec_helper.rb +21 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f4201074868331f8ba3a418613dc76decb79e49f
|
4
|
+
data.tar.gz: 688cd395ea12d10737f0e905c5d1b8b5f8c05426
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b8726335d0d69ac6c9174567a2387eefd662468eaa4fe323682dfbff8b4ec10c7377b7dee22838411fb95d8a17e027e70364e8c117c1fd742f78c6e858392619
|
7
|
+
data.tar.gz: dc61a3a26d12f328255be0ef915427b2be1d4ed5ffc1cc07c0558e387db3ccaff7d91cfa01d7e07d4eb1ea50f6a2663f9a4263640ae301c9fd31a448f2e46990
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Guillaume Montard
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Helioth'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
require 'bundler/gem_tasks'
|
21
|
+
|
22
|
+
# Default directory to look in is `/specs`
|
23
|
+
# Run with `rake spec`
|
24
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
25
|
+
task.rspec_opts = ['--color', '--format', 'documentation']
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => :spec
|
data/lib/helioth.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "helioth/version"
|
2
|
+
require 'helioth/dsl'
|
3
|
+
require 'helioth/role'
|
4
|
+
require 'helioth/relation'
|
5
|
+
require 'helioth/features'
|
6
|
+
require 'helioth/feature'
|
7
|
+
require 'helioth/action'
|
8
|
+
require 'helioth/controller_additions'
|
9
|
+
require 'helioth/controller_resource'
|
10
|
+
require 'helioth/model_additions'
|
11
|
+
|
12
|
+
module Helioth
|
13
|
+
|
14
|
+
def self.dsl(file = nil)
|
15
|
+
Helioth.const_set("DSL", Helioth::Dsl.load(file)) unless const_defined?("DSL")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.const_missing(name)
|
19
|
+
if name == :DSL
|
20
|
+
Helioth.dsl
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Helioth
|
2
|
+
class Action
|
3
|
+
|
4
|
+
attr_accessor :name, :feature
|
5
|
+
|
6
|
+
def initialize(name, &block)
|
7
|
+
@name = name
|
8
|
+
@locales = I18n.available_locales
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def status(status=nil)
|
13
|
+
@status ||= status
|
14
|
+
end
|
15
|
+
|
16
|
+
def locales(*locales)
|
17
|
+
unless locales.empty?
|
18
|
+
@locales = locales
|
19
|
+
end
|
20
|
+
@locales
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Helioth
|
2
|
+
module ControllerAdditions
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
base.helper_method :access_to?, :locale_access_to?, :user_access_to?, :instance_access_to?
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def load_and_authorize_for(*args)
|
10
|
+
ControllerResource.add_before_filter(self, :load_and_authorize_for, *args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def access_to?(feature, *actions)
|
15
|
+
return false if !locale_access_to?(feature, *actions)
|
16
|
+
return true if DSL.roles.user.present? && user_access_to?(feature, *actions)
|
17
|
+
return true if DSL.roles.instance.present? && instance_access_to?(feature, *actions)
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
def locale_access_to?(feature, *actions)
|
22
|
+
DSL.authorized_for_locale?(feature, actions, I18n.locale)
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_access_to?(feature, *actions)
|
26
|
+
DSL.authorized_for_user?(feature, actions, current_user.helioth_role?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def instance_access_to?(feature, *actions)
|
30
|
+
DSL.authorized_for_instance?(feature, actions, current_instance.helioth_role?)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if defined? ActionController::Base
|
36
|
+
ActionController::Base.class_eval do
|
37
|
+
include Helioth::ControllerAdditions
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Helioth
|
2
|
+
|
3
|
+
class ControllerResource
|
4
|
+
|
5
|
+
def self.add_before_filter(controller_class, method, *args)
|
6
|
+
feature = args.first
|
7
|
+
options = args.extract_options!
|
8
|
+
|
9
|
+
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
|
10
|
+
controller_class.send(:before_filter, options.slice(:only, :except, :if, :unless)) do |controller|
|
11
|
+
ControllerResource.new(controller, feature, (options.slice(:action, :actions)).values).send(method)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(controller, feature, *actions)
|
16
|
+
@controller = controller
|
17
|
+
@feature = feature
|
18
|
+
@actions = actions.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_and_authorize_for
|
22
|
+
unless @controller.access_to?(@feature, @actions)
|
23
|
+
##TODO change the behavior based on rails env
|
24
|
+
Rails.logger.info("Access to controller forbidden for feature :#{@feature}")
|
25
|
+
@controller.render :text=>"Access forbidden", :status=>403
|
26
|
+
else
|
27
|
+
Rails.logger.debug("Access to controller granted for feature :#{@feature}")
|
28
|
+
Rails.logger.debug("Access to controller granted for actions #{@actions.inspect}") if @actions.present?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/helioth/dsl.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Helioth
|
2
|
+
class Dsl
|
3
|
+
|
4
|
+
DSL_FILE = Pathname.new(Rails.root || '').join("config", "helioth.rb").to_s unless defined? DSL_FILE
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
Rails.logger.debug("Loading the DSL for the first time")
|
8
|
+
end
|
9
|
+
|
10
|
+
##Should be loaded only one time using Helioth::DSL.dsl
|
11
|
+
def self.load(file = nil)
|
12
|
+
dsl = new
|
13
|
+
dsl.instance_eval(file.nil? ? File.read(DSL_FILE) : File.read(file))
|
14
|
+
return(dsl)
|
15
|
+
rescue Errno::ENOENT
|
16
|
+
raise "Helioth::DSL DSL file missing"
|
17
|
+
end
|
18
|
+
|
19
|
+
## In case the DSL is not well used
|
20
|
+
def method_missing(method_name, *args, &block)
|
21
|
+
raise "No such method #{__method__} in #{__FILE__}"
|
22
|
+
end
|
23
|
+
|
24
|
+
## Configure roles
|
25
|
+
def roles(&block)
|
26
|
+
@roles ||= Role.new(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
## Configure relations
|
30
|
+
def relations(&block)
|
31
|
+
@relations ||= Relation.new(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
## Configure features
|
35
|
+
def features(&block)
|
36
|
+
if block
|
37
|
+
@features ||= Features.new(&block)
|
38
|
+
else
|
39
|
+
@features.list
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
## Get feature
|
44
|
+
def feature(feature_name)
|
45
|
+
@features.list.map{|feature|
|
46
|
+
feature if feature.name == feature_name
|
47
|
+
}.compact.first
|
48
|
+
end
|
49
|
+
|
50
|
+
## Get feature action
|
51
|
+
def action(feature_name, action_name)
|
52
|
+
feature(feature_name).actions.map{|action|
|
53
|
+
action if action.name == action_name
|
54
|
+
}.compact.first
|
55
|
+
end
|
56
|
+
|
57
|
+
## Check authorization
|
58
|
+
def authorized_for_locale?(feature_name, *actions_name, locale)
|
59
|
+
authorized_for(feature_name, actions_name.flatten, {locale: locale})
|
60
|
+
end
|
61
|
+
|
62
|
+
def authorized_for_user?(feature_name, *actions_name, role)
|
63
|
+
authorized_for(feature_name, actions_name.flatten, {role: role, type: :user})
|
64
|
+
end
|
65
|
+
|
66
|
+
def authorized_for_instance?(feature_name, *actions_name, role)
|
67
|
+
authorized_for(feature_name, actions_name.flatten, {role: role, type: :instance})
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def authorized_for(feature_name, actions_name, options={})
|
72
|
+
|
73
|
+
role = options[:role]
|
74
|
+
type = options[:type]
|
75
|
+
locale = options[:locale]
|
76
|
+
|
77
|
+
feature, actions = process_input(feature_name, actions_name)
|
78
|
+
|
79
|
+
if feature
|
80
|
+
|
81
|
+
## If a feature doesn"t have relation (ex: disabled feature)
|
82
|
+
return false if role.present? && relations.feature[feature.status].blank?
|
83
|
+
|
84
|
+
access = Array.new
|
85
|
+
|
86
|
+
if actions.any?
|
87
|
+
access += actions.map{|action|
|
88
|
+
if role.present?
|
89
|
+
relations.feature[action.status][type].include?(role)
|
90
|
+
elsif locale.present?
|
91
|
+
action.locales.include?(locale)
|
92
|
+
end
|
93
|
+
}
|
94
|
+
else
|
95
|
+
access << relations.feature[feature.status][type].include?(role) if role.present?
|
96
|
+
access << feature.locales.include?(locale) if locale.present?
|
97
|
+
end
|
98
|
+
|
99
|
+
access.all?
|
100
|
+
else
|
101
|
+
Rails.logger.info("Feature #{feature.try(:name)} not found")
|
102
|
+
false
|
103
|
+
end
|
104
|
+
rescue
|
105
|
+
raise "Error in method #{__method__} of #{__FILE__}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_input(feature_name, actions_name)
|
109
|
+
feature = feature(feature_name)
|
110
|
+
actions = actions_name.flatten.map{|action_name|
|
111
|
+
action(feature_name, action_name)
|
112
|
+
}
|
113
|
+
return([feature, actions])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Helioth
|
2
|
+
class Feature
|
3
|
+
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
def initialize(name, &block)
|
7
|
+
@actions = Array.new
|
8
|
+
@name = name
|
9
|
+
@locales = I18n.available_locales
|
10
|
+
instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def status(status = nil)
|
14
|
+
@status ||= status
|
15
|
+
end
|
16
|
+
|
17
|
+
def actions(*actions, &block)
|
18
|
+
if block.nil?
|
19
|
+
@actions
|
20
|
+
else
|
21
|
+
actions.each{|action|
|
22
|
+
@actions << Action.new(action, &block)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def action(action_name)
|
28
|
+
@actions.map{|action|
|
29
|
+
action if action.name == action_name
|
30
|
+
}.compact.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def locales(*locales)
|
34
|
+
unless locales.empty?
|
35
|
+
@locales = locales
|
36
|
+
end
|
37
|
+
@locales
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Helioth
|
2
|
+
module ModelAdditions
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def has_helioth_role(*args)
|
9
|
+
options = args.extract_options!
|
10
|
+
@@role_column = options[:column] || :role
|
11
|
+
@@role_instance = args.first
|
12
|
+
|
13
|
+
add_role_validation
|
14
|
+
|
15
|
+
define_method "#{@@role_column}?" do
|
16
|
+
eval("#{@@role_column}.to_sym")
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method "helioth_role?" do
|
20
|
+
eval("#{@@role_column}?")
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method "is_#{@@role_column}?" do |arg|
|
24
|
+
eval("self.#{@@role_column}.to_sym") == arg.to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_role_validation
|
29
|
+
self.send(:validates, @@role_column.to_sym, inclusion: { in: available_roles, message: "%{value} is not a valid value" }, allow_blank: true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def available_roles
|
33
|
+
case @@role_instance when :user
|
34
|
+
roles = DSL.roles.user.map(&:to_s)
|
35
|
+
when :instance
|
36
|
+
roles = DSL.roles.instance.map(&:to_s)
|
37
|
+
else
|
38
|
+
raise "Invalid option #{options} for method #{__method__}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if defined? ActiveRecord::Base
|
46
|
+
ActiveRecord::Base.class_eval do
|
47
|
+
include Helioth::ModelAdditions
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Helioth
|
2
|
+
class Relation
|
3
|
+
|
4
|
+
def initialize(&block)
|
5
|
+
@feature = Hash.new
|
6
|
+
instance_eval(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def feature(status = nil, &block)
|
10
|
+
if block.nil?
|
11
|
+
@feature
|
12
|
+
else
|
13
|
+
@@tmp = Hash.new
|
14
|
+
instance_eval(&block)
|
15
|
+
@feature[status] = @@tmp
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance(*status)
|
20
|
+
@@tmp.merge!({instance: status})
|
21
|
+
end
|
22
|
+
|
23
|
+
def user(*status)
|
24
|
+
@@tmp.merge!({user: status})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/helioth/role.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Helioth
|
2
|
+
class Role
|
3
|
+
def initialize(&block)
|
4
|
+
instance_eval(&block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def user(*user)
|
8
|
+
@user ||= user
|
9
|
+
end
|
10
|
+
|
11
|
+
def instance(*instance)
|
12
|
+
@instance ||= instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def feature(*feature)
|
16
|
+
@feature ||= feature
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|