right_on 0.1.0

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