helioth 0.1.0

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.
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
@@ -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,15 @@
1
+ module Helioth
2
+ class Features
3
+
4
+ attr_accessor :list
5
+
6
+ def initialize(&block)
7
+ @list = Array.new
8
+ instance_eval(&block)
9
+ end
10
+
11
+ def feature(name, &block)
12
+ @list << Feature.new(name, &block)
13
+ end
14
+ end
15
+ 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
@@ -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