right_on 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: 0d604e3a0d52e8dd395888dce6b1d201138ea8b6
4
+ data.tar.gz: 62d8d15da688d03422d5a31b3641a577f92ec42b
5
+ SHA512:
6
+ metadata.gz: 59b6d8bbf60c6328bfc0753b622a05603d53362a762f47f490b1cf39cf8f083f4e0423616fdc28f99e088db2d77d16052e44f4ea8fc8f99daf71e990aab9b3b0
7
+ data.tar.gz: 829f2b6d9050d1cd92b38f9e70cda0a41e325b72d35cc77443e2e70a17c382f3718c983ca41799eec0177131876ec0ba45b49d0c9018a9edba488552ad044173
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg
2
+ coverage
3
+ Gemfile.lock
4
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ script: "bundle exec rake spec"
5
+ gemfile:
6
+ - gemfiles/rails3.gemfile
7
+ - gemfiles/rails4.gemfile
8
+ notifications:
9
+ email:
10
+ - support@travellink.com.au
11
+ flowdock:
12
+ secure: TZTbtSK+LDly7dRu0eYE3oro7fH0dktkyzeRo67ofPqO5Xdor6U6Eh2njeq6vqiL9tI+2V1MLv90I9tcLrbiz609sY6r+4byL8fQoDRjXMYcOr8bOcOSMldmKgf/42XmxgcFl9Xh+lWkdIdL9e4xdtsytbuK/EVGfA/DEW7E7DE=
13
+ sudo: false
14
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development, :test do
5
+ gem 'rails'
6
+ gem 'dependent_restrict', github: 'sealink/dependent_restrict', branch: 'master'
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Tom Preston-Werner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ Right On
2
+ ========
3
+
4
+ [![Build Status](https://travis-ci.org/sealink/right_on.png?branch=master)](https://travis-ci.org/sealink/right_on)
5
+ [![Coverage Status](https://coveralls.io/repos/sealink/right_on/badge.png)](https://coveralls.io/r/sealink/right_on)
6
+ [![Dependency Status](https://gemnasium.com/sealink/right_on.png?travis)](https://gemnasium.com/sealink/right_on)
7
+ [![Code Climate](https://codeclimate.com/github/sealink/right_on.png)](https://codeclimate.com/github/sealink/right_on)
8
+
9
+
10
+ # DESCRIPTION
11
+
12
+ Gives rails applications a way to manage rights/roles
13
+
14
+ If you have a class User, then you can use it like so:
15
+
16
+ ```ruby
17
+ class User < ActiveRecord::Base
18
+ include RightOn::RoleModel
19
+ end
20
+ ```
21
+
22
+ This will create a many-to-many relationship with roles
23
+
24
+ Roles are sets of rights. Generally people will have multiple roles
25
+ e.g. A senior bank teller might have the following roles:
26
+ * Senior Bank Teller
27
+ * Bank Teller
28
+ * Bank Employee
29
+
30
+ The Role class also has a many-to-many relationship with rights
31
+
32
+ So a bank employee might have access to the building during regular hours
33
+ e.g. has a right 'transactions/add' giving him access to the add method of the transactions controller
34
+
35
+ Wheras the senior bank teller might be the only one with the 'tellers/create'
36
+ Thus he is the only one who can create new tellers.
37
+
38
+ There are a few types of rights:
39
+ * Rights giving access to an entire controller (tellers)
40
+ * Rights giving access to a single action within a controller (e.g. tellers/show)
41
+ * Rights giving access to multiple actions within a controller (e.g. tellers/read_only or tellers/read_write)
42
+ * Rights giving access to particular objects, e.g. a right gives you access to contact clients with a type "High Value Clients"
43
+ * Rights giving custom access. To have affect you need to use the has_right? Helper in you views
44
+
45
+ RightOn comes with controller methods to verify if the user has rights. Simply add the following in your app to controllers
46
+ you want to enforce rights:
47
+
48
+ ```ruby
49
+ include RightOn::ActionControllerExtensions
50
+
51
+ before_filter :verify_rights
52
+ ```
53
+
54
+ This will enforce that you have a right matching the controllers right
55
+ You must have a method "current_user" which is the user model that you've made as the RoleModel
56
+
57
+ # INSTALLATION
58
+
59
+ Add to your Gemfile:
60
+ gem 'right_on'
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Default: run specs.'
4
+ task :default => :spec
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
12
+ end
data/db/migration.rb ADDED
@@ -0,0 +1,34 @@
1
+ class RightOnMigration < ActiveRecord::Migration
2
+ def self.change
3
+ create_table :rights do |t|
4
+ t.string :name, :controller, :action, :limit => 150
5
+ t.timestamps
6
+ end
7
+
8
+ change_table :rights do |t|
9
+ t.index :action
10
+ t.index :name
11
+ t.index [:controller, :action]
12
+ end
13
+
14
+ create_table :rights_roles, :id => false do |t|
15
+ t.integer :right_id, :role_id
16
+ end
17
+
18
+ change_table :rights_roles do |t|
19
+ t.index [:right_id, :role_id]
20
+ t.index [:role_id, :right_id]
21
+ end
22
+
23
+ create_table :roles do |t|
24
+ t.string :title
25
+ t.text :description
26
+ t.integer :right_id
27
+ t.timestamps
28
+ end
29
+
30
+ change_table :roles do |t|
31
+ t.index :right_id
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ rights:
2
+ general:
3
+ - models:
4
+ - index
5
+ - view
6
+ - change
7
+ admin:
8
+ - users
9
+
10
+ roles:
11
+ simple_role:
12
+ - models@index
13
+ advanced_role:
14
+ - models
15
+ - models@index
16
+ - users
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ gemspec :path => '../'
3
+
4
+ group :development, :test do
5
+ gem 'rails', '~> 3.2.0'
6
+ gem 'rails_4_backports' # find_by
7
+ end
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec :path => '../'
3
+
4
+ group :development, :test do
5
+ gem 'rails', '~> 4.2'
6
+ end
@@ -0,0 +1,68 @@
1
+ module RightOn
2
+
3
+ module ActionControllerExtensions
4
+
5
+ def self.included(base)
6
+ base.module_eval do
7
+ helper_method :access_allowed?, :access_allowed_to_controller?
8
+ class_attribute :rights_from
9
+ class_attribute :permission_denied_layout
10
+ end
11
+ end
12
+
13
+ # Checks the access privilege of the user and renders permission_denied page if required
14
+ def verify_rights
15
+ access_allowed?(controller_action_options) || permission_denied
16
+ end
17
+
18
+ # Checks the access privilege for a controller
19
+ def access_allowed_to_controller?(controller)
20
+ controller_class = "#{controller.to_s.camelcase}Controller".safe_constantize
21
+
22
+ # Handle inheritance of rights
23
+ if controller_class && controller_class.rights_from.present?
24
+ controller = controller_class.rights_from.to_s
25
+ end
26
+
27
+ access_allowed?(controller)
28
+ end
29
+
30
+ # Checks the access privilege of the user and returns true or false
31
+ def access_allowed?(opts={})
32
+ if opts.is_a?(String)
33
+ controller, action = opts.split('#')
34
+ opts = {:controller => controller, :action => action}
35
+ end
36
+ opts[:controller] ||= params[:controller]
37
+ opts[:action] ||= params[:action]
38
+ current_user.rights.any? { |r| r.allowed?(opts.slice(:controller, :action)) }
39
+ end
40
+
41
+ # Called if a security check determines permission is denied
42
+ def permission_denied
43
+ @permission_denied_response = RightOn::PermissionDeniedResponse.new(params, controller_action_options)
44
+
45
+ respond_to do |format|
46
+ format.html { render status: 401, template: 'permission_denied', layout: (permission_denied_layout || false) }
47
+ format.json do
48
+ render status: 401, json: @permission_denied_response.to_json
49
+ end
50
+ format.js do
51
+ render :update, status: 401 do |page|
52
+ page.alert(@permission_denied_layout.text_message)
53
+ end
54
+ end
55
+ end
56
+
57
+ false
58
+ end
59
+
60
+ def controller_action_options
61
+ opts = params.slice(:controller, :action)
62
+ opts[:controller] = rights_from.to_s if rights_from
63
+ opts
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a new database migration for adding a right.
3
+ Pass the right name and an optional list of attribute pairs as arguments.
4
+
5
+ A migration class is generated in db/migrate prefixed by a timestamp of the current date and time.
6
+
7
+ Example:
8
+ `rails generate right_on:right_migration my_controllers/my_new_controller#my_new_action`
@@ -0,0 +1,59 @@
1
+ module RightOn
2
+ module Generators
3
+ class RightMigrationGenerator < Rails::Generators::Base
4
+
5
+ include Rails::Generators::Migration
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ argument :name, :type => :string, :required => false
10
+
11
+ class_option :controller, :type => :string, :required => false,
12
+ :desc => "Indicates the right's controller"
13
+ class_option :action, :type => :string, :required => false,
14
+ :desc => "Indicates the right's action"
15
+ class_option :right, :type => :string, :required => false,
16
+ :desc => "Indicates an existing right. Any role including this right will also include the new right"
17
+
18
+
19
+ def generate_migration
20
+ raise ArgumentError, "Either name or controller must be specified" if right_controller.blank?
21
+ migration_template "right_migration.rb", "db/migrate/add_#{parsed_right_name}_right.rb"
22
+ end
23
+
24
+
25
+ # Implement the required interface for Rails::Generators::Migration.
26
+ def self.next_migration_number(dirname) #:nodoc:
27
+ next_migration_number = current_migration_number(dirname) + 1
28
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
29
+ end
30
+
31
+ private
32
+
33
+ def right_controller
34
+ (options[:controller] || name.to_s.split('#')[0]).to_s.underscore.presence
35
+ end
36
+
37
+
38
+ def right_action
39
+ (options[:action] || name.to_s.split('#')[1]).to_s.underscore.presence
40
+ end
41
+
42
+
43
+ def right_name
44
+ name.presence || [right_controller, right_action].compact.join('#')
45
+ end
46
+
47
+
48
+ def right_for_roles
49
+ options[:right]
50
+ end
51
+
52
+
53
+ def parsed_right_name
54
+ right_name.gsub('/','_').gsub('#','_').underscore
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ class Add<%= parsed_right_name.camelize %>Right < ActiveRecord::Migration
2
+
3
+ class Right < ActiveRecord::Base
4
+ has_and_belongs_to_many :roles
5
+
6
+ validates_presence_of :name
7
+ validates_uniqueness_of :name
8
+ end
9
+
10
+ class Role < ActiveRecord::Base
11
+ has_and_belongs_to_many :rights
12
+
13
+ validates_presence_of :title
14
+ validates_uniqueness_of :title
15
+ end
16
+
17
+ def self.up
18
+ right_for_roles = Right.find_by_name("<%= right_for_roles %>")
19
+ Right.create(
20
+ :controller => '<%= right_controller %>'.presence,
21
+ :action => '<%= right_action %>'.presence,
22
+ :name => '<%= right_name %>'.presence,
23
+ :roles => right_for_roles.roles
24
+ )
25
+ end
26
+
27
+
28
+ def self.down
29
+ Right.destroy_all(:name => '<%= right_name %>')
30
+ end
31
+
32
+ end
@@ -0,0 +1,43 @@
1
+ module RightOn
2
+ class PermissionDeniedResponse
3
+ attr_reader :right_allowed, :roles_allowed, :controller_name
4
+
5
+ def initialize(params, controller_action_options)
6
+ @params = params
7
+ @right_allowed = Right.all.detect{|right| right.allowed?(controller_action_options)}
8
+ @roles_allowed = @right_allowed.roles if @right_allowed
9
+ @controller_name = @params[:controller] unless @right_allowed
10
+ end
11
+
12
+ def text_message
13
+ if @right_allowed
14
+ <<-MESSAGE
15
+ You are not authorised to perform the requested operation.
16
+ Right required: #{@right_allowed}
17
+ This right is given to the following roles: #{@roles_allowed.map(&:title).join(", ")}.
18
+ Contact your system manager to be given this right.
19
+ MESSAGE
20
+ else
21
+ no_right_for_page
22
+ end
23
+ end
24
+
25
+ def to_json
26
+ {
27
+ error: 'Permission Denied',
28
+ right_allowed: (@right_allowed ? @right_allowed.name : no_right_for_page),
29
+ roles_for_right: (@roles_allowed ? @roles_allowed.map(&:title) : no_roles_for_page)
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def no_right_for_page
36
+ "No right is defined for this page: #{@controller_name}. Contact your system manager to notify this problem."
37
+ end
38
+
39
+ def no_roles_for_page
40
+ 'N/A (as no right is assigned for this action)'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ require 'right_on/role_model'
2
+ require 'right_on/right'
3
+ require 'right_on/role'
4
+ require 'right_on/action_controller_extensions'
5
+ require 'right_on/permission_denied_response'
@@ -0,0 +1,12 @@
1
+ class Railtie < Rails::Railtie
2
+ initializer 'right_on.initialize' do
3
+ ActiveSupport.on_load(:active_record) do
4
+ require 'right_on/rails'
5
+ end
6
+ end
7
+
8
+ rake_tasks do
9
+ load "right_on/tasks/seeds_rights.rake"
10
+ load "right_on/tasks/rights_roles.rake"
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ module RightOn
2
+ module RestrictedByRight
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def restricted_by_right(options = {})
10
+ options ||= {}
11
+ group = options.fetch(:group, 'other')
12
+
13
+ @right_on_config ||= {}
14
+ @right_on_config[:restricted_by_right_group] = group
15
+
16
+ Right.associate_group(self, group)
17
+
18
+ class << self
19
+ def accessible_to(user)
20
+ all.select{|o| user.rights.include?(o.right)}
21
+ end
22
+ end
23
+
24
+ include InstanceMethods
25
+
26
+ belongs_to :right, :class_name => 'RightOn::Right'
27
+ before_create :create_access_right!
28
+ after_destroy :destroy_access_right!
29
+ end
30
+
31
+ def restricted_by_right_group
32
+ (@right_on_config || {})[:restricted_by_right_group]
33
+ end
34
+ end
35
+
36
+ module InstanceMethods
37
+
38
+ private
39
+
40
+ def create_access_right!
41
+ right_name = "#{self.class.name.titleize}: #{name}"
42
+ self.right = find_right(right_name) || Right.create!(:name => right_name)
43
+ end
44
+
45
+ def find_right(name)
46
+ Right.find_by(:name => name)
47
+ end
48
+
49
+ def destroy_access_right!
50
+ self.right.try(:destroy)
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,171 @@
1
+ require 'active_record'
2
+
3
+ module RightOn
4
+ class Right < ActiveRecord::Base
5
+
6
+ has_and_belongs_to_many :roles, :class_name => 'RightOn::Role'
7
+
8
+ validates_presence_of :name
9
+ validates_uniqueness_of :name
10
+
11
+ scope :ordered, -> { order :name }
12
+
13
+ after_save :clear_cache
14
+ after_destroy :clear_cache
15
+
16
+ attr_accessor :group
17
+
18
+ class << self
19
+ @@restricted_by_right_classes = []
20
+
21
+ def associate_group(klass, group)
22
+ # Prevent issues when reloading class using restricted_by_right
23
+ unless @@restricted_by_right_classes.include?(klass)
24
+ @@restricted_by_right_classes << klass
25
+ end
26
+ has_one klass.table_name.singularize.to_sym, :dependent => :restrict
27
+ end
28
+
29
+ def rights_yaml(file_path)
30
+ @@rights_yaml = file_path
31
+ end
32
+
33
+ def by_groups
34
+ rights = []
35
+ rights += regular_rights_with_group
36
+ rights += restricted_rights_with_group
37
+ rights.group_by(&:group)
38
+ end
39
+
40
+ def regular_rights_with_group
41
+ yaml = YAML::load_file(@@rights_yaml)
42
+ rights = []
43
+ rights_by_name = Hash[Right.all.map{|r| [r.name, r]}]
44
+ yaml['rights'].each_pair do |group, right_names|
45
+ rights_for_group = []
46
+ right_names.each do |right_name|
47
+ if right_name.is_a?(String) # controller
48
+ r = rights_by_name[right_name]
49
+ raise right_name if r.nil?
50
+ rights_for_group << r
51
+ else right_name.is_a?(Hash) # controller + actions
52
+ controller, actions = right_name.first
53
+ r = rights_by_name[controller]
54
+ if r
55
+ rights_for_group << r
56
+ end
57
+ actions.each do |action|
58
+ name = "#{controller}##{action}"
59
+ r = rights_by_name[name]
60
+ raise name.inspect + "****" + right_name.inspect + '---' + action_right.inspect if r.nil?
61
+ rights_for_group << r
62
+ end
63
+ end
64
+ end
65
+ rights_for_group.each{|r| r.group = group}
66
+ rights += rights_for_group
67
+ end
68
+ rights
69
+ end
70
+
71
+ def restricted_rights_with_group
72
+ rights = []
73
+ @@restricted_by_right_classes.each do |klass|
74
+ group = klass.restricted_by_right_group
75
+ rights += all_rights(klass).map(&:right).each do |right|
76
+ right.group = group
77
+ end
78
+ end
79
+ rights
80
+ end
81
+
82
+ def all_rights(klass)
83
+ klass.includes(:right).all
84
+ end
85
+ end
86
+
87
+ # Is this right allowed for the given context?
88
+ #
89
+ # Context params is an option hash:
90
+ # :controller => controller name
91
+ # :action => action name
92
+ #
93
+ # The context tells us the state of the request being made.
94
+
95
+ def allowed?(context={})
96
+ return false unless controller == context[:controller]
97
+ if action
98
+ action_permitted?(context[:action])
99
+ else
100
+ # right without action works if no specific right exists
101
+ # e.g. can't edit if there's a edit or change right defined
102
+ # as you must used that specific right
103
+ specific_rights = Array(APPLICABLE_RIGHTS[context[:action].to_sym]) + [context[:action]]
104
+ specific_rights.all?{|action| Right["#{context[:controller]}##{action}"].nil?}
105
+ end
106
+ end
107
+
108
+ APPLICABLE_RIGHTS = {
109
+ :new => [:change],
110
+ :edit => [:change],
111
+ :update => [:change],
112
+ :create => [:change],
113
+ :destroy => [:change],
114
+ :index => [:change, :view],
115
+ :show => [:change, :view]
116
+ }
117
+
118
+ CHANGE_ACTIONS = %w(new edit update create destroy index show)
119
+
120
+ VIEW_ACTIONS = %w(index show)
121
+
122
+ def action_permitted?(context_action)
123
+ case action.to_sym
124
+ when :change
125
+ CHANGE_ACTIONS.include?(context_action)
126
+ when :view
127
+ VIEW_ACTIONS.include?(context_action)
128
+ else
129
+ action == context_action
130
+ end
131
+ end
132
+
133
+ def sensible_name
134
+ name.humanize.titleize.gsub(/#/, ' - ')
135
+ end
136
+
137
+ def to_s
138
+ name
139
+ end
140
+
141
+ def self.cache
142
+ @@cache ||= Rails.cache
143
+ end
144
+
145
+ def self.cache=(cache)
146
+ @@cache = cache
147
+ end
148
+
149
+ def self.clear_cache
150
+ cache.delete('Right.all')
151
+ end
152
+
153
+ def clear_cache
154
+ self.class.clear_cache
155
+ end
156
+
157
+ attr_accessor :rights
158
+ def self.[](name)
159
+ @rights = cache.read('Right.all') || calculate_and_write_cache
160
+ @rights[name]
161
+ end
162
+
163
+ private
164
+ def self.calculate_and_write_cache
165
+ right_cache = Hash[Right.all.map{|r|[r.name, r.id]}]
166
+ cache.write('Right.all', right_cache) or raise RuntimeError, "Could not cache rights"
167
+ right_cache
168
+ end
169
+
170
+ end
171
+ end