action_access 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/CONTRIBUTING.md +23 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +87 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +283 -0
  8. data/Rakefile +28 -0
  9. data/action_access.gemspec +24 -0
  10. data/lib/action_access.rb +14 -0
  11. data/lib/action_access/controller_additions.rb +75 -0
  12. data/lib/action_access/keeper.rb +90 -0
  13. data/lib/action_access/model_additions.rb +14 -0
  14. data/lib/action_access/railtie.rb +19 -0
  15. data/lib/action_access/user_utilities.rb +39 -0
  16. data/lib/action_access/version.rb +3 -0
  17. data/test/action_access_test.rb +21 -0
  18. data/test/controllers/articles_controller_test.rb +41 -0
  19. data/test/controllers/secrets_controller_test.rb +10 -0
  20. data/test/controllers/static_controller_test.rb +15 -0
  21. data/test/dummy/README.rdoc +28 -0
  22. data/test/dummy/Rakefile +6 -0
  23. data/test/dummy/app/assets/images/.keep +0 -0
  24. data/test/dummy/app/assets/javascripts/application.js +13 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  26. data/test/dummy/app/controllers/application_controller.rb +15 -0
  27. data/test/dummy/app/controllers/articles_controller.rb +36 -0
  28. data/test/dummy/app/controllers/concerns/.keep +0 -0
  29. data/test/dummy/app/controllers/secrets_controller.rb +4 -0
  30. data/test/dummy/app/controllers/static_controller.rb +6 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/mailers/.keep +0 -0
  33. data/test/dummy/app/models/.keep +0 -0
  34. data/test/dummy/app/models/concerns/.keep +0 -0
  35. data/test/dummy/app/models/user.rb +7 -0
  36. data/test/dummy/app/views/articles/edit.html.erb +1 -0
  37. data/test/dummy/app/views/articles/index.html.erb +1 -0
  38. data/test/dummy/app/views/articles/new.html.erb +1 -0
  39. data/test/dummy/app/views/articles/show.html.erb +3 -0
  40. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  41. data/test/dummy/app/views/static/home.html.erb +1 -0
  42. data/test/dummy/bin/bundle +3 -0
  43. data/test/dummy/bin/rails +4 -0
  44. data/test/dummy/bin/rake +4 -0
  45. data/test/dummy/config.ru +4 -0
  46. data/test/dummy/config/application.rb +23 -0
  47. data/test/dummy/config/boot.rb +5 -0
  48. data/test/dummy/config/database.yml +25 -0
  49. data/test/dummy/config/environment.rb +5 -0
  50. data/test/dummy/config/environments/development.rb +37 -0
  51. data/test/dummy/config/environments/production.rb +78 -0
  52. data/test/dummy/config/environments/test.rb +39 -0
  53. data/test/dummy/config/initializers/assets.rb +8 -0
  54. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  55. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  56. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  57. data/test/dummy/config/initializers/inflections.rb +16 -0
  58. data/test/dummy/config/initializers/mime_types.rb +4 -0
  59. data/test/dummy/config/initializers/session_store.rb +3 -0
  60. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  61. data/test/dummy/config/locales/en.yml +23 -0
  62. data/test/dummy/config/routes.rb +6 -0
  63. data/test/dummy/config/secrets.yml +22 -0
  64. data/test/dummy/db/migrate/20140926071026_create_users.rb +10 -0
  65. data/test/dummy/db/schema.rb +23 -0
  66. data/test/dummy/lib/assets/.keep +0 -0
  67. data/test/dummy/log/.keep +0 -0
  68. data/test/dummy/public/404.html +67 -0
  69. data/test/dummy/public/422.html +67 -0
  70. data/test/dummy/public/500.html +66 -0
  71. data/test/dummy/public/favicon.ico +0 -0
  72. data/test/models/user_test.rb +22 -0
  73. data/test/support/compact_environment.rb +9 -0
  74. data/test/test_helper.rb +8 -0
  75. metadata +203 -0
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'rdoc/task'
9
+ require 'rake/testtask'
10
+
11
+
12
+ RDoc::Task.new(:rdoc) do |rdoc|
13
+ rdoc.rdoc_dir = 'rdoc'
14
+ rdoc.title = 'ActionAccess'
15
+ rdoc.options << '--line-numbers'
16
+ rdoc.rdoc_files.include('README.md')
17
+ rdoc.rdoc_files.include('lib/**/*.rb')
18
+ end
19
+
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.libs << 'lib'
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+
28
+ task default: :test
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'action_access/version'
4
+
5
+ # Gem description and dependencies
6
+ Gem::Specification.new do |s|
7
+ s.name = 'action_access'
8
+ s.version = ActionAccess::VERSION
9
+ s.authors = ['Matías A. Gagliano']
10
+ s.email = ['matias.gagliano@gmail.com']
11
+ s.homepage = 'https://github.com/matiasgagliano/action_access'
12
+ s.summary = 'Access control system for Ruby on Rails.'
13
+ s.description = 'Easy and modular way to secure applications and handle permissions.'
14
+ s.license = 'MIT'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- test/*`.split("\n")
18
+ s.require_paths = ['lib']
19
+
20
+ s.add_dependency 'rails', '~> 4.1'
21
+
22
+ s.add_development_dependency 'sqlite3'
23
+ end
24
+
@@ -0,0 +1,14 @@
1
+ require 'singleton'
2
+ require 'action_access/version'
3
+ require 'action_access/railtie'
4
+
5
+ module ActionAccess
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :Keeper
10
+ autoload :ControllerAdditions
11
+ autoload :ModelAdditions
12
+ autoload :UserUtilities
13
+ end
14
+ end
@@ -0,0 +1,75 @@
1
+ module ActionAccess
2
+ module ControllerAdditions
3
+ module ClassMethods
4
+ # Lock actions by default, they won't be accessible unless authorized.
5
+ # It takes the same options as filter callbacks.
6
+ def lock_access(options = {})
7
+ before_action :validate_access!, options
8
+ end
9
+
10
+ # Is this controller locked?
11
+ def access_locked?
12
+ filters = _process_action_callbacks.collect(&:filter)
13
+ :validate_access!.in? filters
14
+ end
15
+
16
+ # Set an access rule for the current controller.
17
+ # It will automatically lock the controller if it wasn't already.
18
+ #
19
+ # == Example:
20
+ # Add the following to ArticlesController to allow admins to edit articles.
21
+ # let :admin, [:edit, :update]
22
+ #
23
+ def let(clearance_level, permissions)
24
+ lock_access unless access_locked?
25
+ keeper = ActionAccess::Keeper.instance
26
+ keeper.let clearance_level, permissions, self
27
+ end
28
+ end
29
+
30
+
31
+ def self.included(base)
32
+ base.extend ClassMethods
33
+ base.helper_method :keeper
34
+ end
35
+
36
+
37
+ private
38
+
39
+ # Helper to access Keeper's instance.
40
+ def keeper
41
+ ActionAccess::Keeper.instance
42
+ end
43
+
44
+ # Clearance level of the current user (override to customize).
45
+ def current_clearance_level
46
+ if defined? current_user and current_user.respond_to?(:clearance_level)
47
+ current_user.clearance_level.to_s.to_sym
48
+ else
49
+ :guest
50
+ end
51
+ end
52
+
53
+ # Default path to redirect any non authorized access (override to customize).
54
+ def unauthorized_access_redirection_path
55
+ root_path
56
+ end
57
+
58
+ # Validate access to the current route.
59
+ def validate_access!
60
+ clearance_level = current_clearance_level
61
+ action = self.action_name
62
+ not_authorized! unless keeper.lets? clearance_level, action, self.class
63
+ end
64
+
65
+ # Redirect if not authorized.
66
+ # May be used inside action methods for finer control.
67
+ def not_authorized!(*args)
68
+ options = args.extract_options!
69
+ message = options[:message] ||
70
+ I18n.t('action_access.redirection_message', default: 'Not authorized.')
71
+ path = options[:path] || unauthorized_access_redirection_path
72
+ redirect_to path, alert: message
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,90 @@
1
+ module ActionAccess
2
+ class Keeper
3
+ include Singleton
4
+
5
+ def initialize
6
+ @rules = {}
7
+ end
8
+
9
+ # Set clearance to perform actions over a resource.
10
+ #
11
+ # Clearance level and resource can be either plural or singular.
12
+ #
13
+ # == Examples:
14
+ # let :user, :show, :profile
15
+ # let :user, :show, @profile
16
+ # let :user, :show, ProfilesController
17
+ # # Any user can can access 'profiles#show'.
18
+ #
19
+ # let :admins, [:edit, :update], :articles, namespace: :admin
20
+ # let :admins, [:edit, :update], @admin_article
21
+ # let :admins, [:edit, :update], Admin::ArticlesController
22
+ # # Admins can access 'admin/articles#edit' and 'admin/articles#update'.
23
+ #
24
+ def let(clearance_level, actions, resource, options = {})
25
+ clearance_level = clearance_level.to_s.singularize.to_sym
26
+ actions = Array(actions).map(&:to_sym)
27
+ controller = get_controller_name(resource, options)
28
+ @rules[controller] ||= {}
29
+ @rules[controller][clearance_level] = actions
30
+ return nil
31
+ end
32
+
33
+ # Check if a given clearance level allows to perform certain action on a resource.
34
+ #
35
+ # Clearance level and resource can be either plural or singular.
36
+ #
37
+ # Examples:
38
+ # lets? :users, :create, :profiles
39
+ # lets? :users, :create, @profile
40
+ # lets? :users, :create, ProfilesController
41
+ # # True if users are allowed to access 'profiles#create'.
42
+ #
43
+ # lets? :admin, :edit, :article, namespace: :admin
44
+ # lets? :admin, :edit, @admin_article
45
+ # lets? :admin, :edit, Admin::ArticlesController
46
+ # # True if any admin is allowed to access 'admin/articles#edit'.
47
+ #
48
+ def lets?(clearance_level, action, resource, options = {})
49
+ clearance_level = clearance_level.to_s.singularize.to_sym
50
+ action = action.to_sym
51
+ controller = get_controller_name(resource, options)
52
+
53
+ # Load the controller to ensure its rules are loaded (lazy loading rules).
54
+ controller.constantize.new
55
+ rules = @rules[controller]
56
+ return false unless rules
57
+
58
+ # Check rules
59
+ Array(rules[:all]).include?(:all) ||
60
+ Array(rules[:all]).include?(action) ||
61
+ Array(rules[clearance_level]).include?(:all) ||
62
+ Array(rules[clearance_level]).include?(action)
63
+ end
64
+
65
+
66
+ private
67
+
68
+ def get_controller_name(resource, options = {})
69
+ # Assume a controller if given a class
70
+ return resource.name if resource.is_a? Class
71
+
72
+ # Assume a model instance if not a string or symbol
73
+ unless resource.is_a?(String) || resource.is_a?(Symbol)
74
+ resource = resource.class.name
75
+ end
76
+
77
+ # Build controller name
78
+ path = options[:namespace].to_s.split(/::|\//).reject(&:blank?).map(&:camelize)
79
+ path << resource.to_s.camelize.pluralize + 'Controller'
80
+ controller = path.join('::')
81
+
82
+ # Make sure that the controller exists.
83
+ # Will throw a NameError exception if resource and/or namespace are wrong.
84
+ controller.constantize
85
+
86
+ # Return controller name
87
+ return controller
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ module ActionAccess
2
+ module ModelAdditions
3
+ module ClassMethods
4
+ # Add Action Access user related utilities to the current model.
5
+ def add_access_utilities
6
+ include ActionAccess::UserUtilities
7
+ end
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module ActionAccess
2
+ class Railtie < Rails::Railtie
3
+ config.eager_load_namespaces << ActionAccess
4
+
5
+ initializer 'action_access.controller_additions' do
6
+ # Extend ActionController::Base
7
+ ActiveSupport.on_load :action_controller do
8
+ include ControllerAdditions
9
+ end
10
+ end
11
+
12
+ initializer 'action_access.model_additions' do
13
+ # Extend ActiveRecord::Base
14
+ ActiveSupport.on_load :active_record do
15
+ include ModelAdditions
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ module ActionAccess
2
+ module UserUtilities
3
+ # Check if the user is authorized to perform a given action.
4
+ #
5
+ # Resource can be either plural or singular.
6
+ #
7
+ # == Examples:
8
+ # user.can? :show, :articles
9
+ # user.can? :show, @article
10
+ # user.can? :show, ArticlesController
11
+ # # True if the user's clearance level allows to access 'articles#show'
12
+ #
13
+ # user.can? :edit, :articles, namespace: :admin
14
+ # user.can? :edit, @admin_article
15
+ # user.can? :edit, Admin::ArticlesController
16
+ # # True if the user's clearance level allows to access 'admin/articles#edit'
17
+ #
18
+ def can?(action, resource, options = {})
19
+ keeper = ActionAccess::Keeper.instance
20
+ keeper.lets? clearance_level, action, resource, options
21
+ end
22
+
23
+
24
+ private
25
+
26
+ # Accessor for the user's clearance level.
27
+ #
28
+ # Must be overridden to set the proper clearance level.
29
+ #
30
+ # == Example:
31
+ # def clearance_level
32
+ # role.name
33
+ # end
34
+ #
35
+ def clearance_level
36
+ :guest
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module ActionAccess
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+ require 'support/compact_environment'
3
+
4
+ class ActionAccessTest < ActiveSupport::TestCase
5
+ test "unauthorized accesses aren't allowed" do
6
+ assert_equal false, keeper.lets?(:user, :edit, :posts)
7
+ assert_equal false, keeper.lets?(:user, :edit, :posts, namespace: :admin)
8
+ end
9
+
10
+ test "authorized accesses are allowed" do
11
+ keeper.let :editor, [:edit, :update], :posts
12
+ assert_equal true, keeper.lets?(:editor, :edit, :posts)
13
+ assert_equal true, keeper.lets?(:editor, :update, :posts)
14
+ end
15
+
16
+ test "authorized accesses within namespaces are allowed" do
17
+ keeper.let :admin, [:new, :create], :posts, namespace: :admin
18
+ assert_equal true, keeper.lets?(:admin, :new, :posts, namespace: :admin)
19
+ assert_equal true, keeper.lets?(:admin, :create, :posts, namespace: :admin)
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class ArticlesControllerTest < ActionController::TestCase
4
+ test "any access with no clearance level gets redirected" do
5
+ # Undefined role
6
+ get :new
7
+ assert_redirected_to root_url
8
+
9
+ post :create
10
+ assert_redirected_to root_url
11
+ end
12
+
13
+ test "any access with undefined clearance level gets redirected" do
14
+ # The role doesn't exist (undefined)
15
+ get :new, nil, {role: :super}
16
+ assert_redirected_to root_url
17
+
18
+ post :create, nil, {role: :super}
19
+ assert_redirected_to root_url
20
+ end
21
+
22
+ test "any unauthorized access gets redirected" do
23
+ # The roles exist but aren't authorized.
24
+ get :new, nil, {role: :user}
25
+ assert_redirected_to root_url
26
+
27
+ post :create, nil, {role: :editor}
28
+ assert_redirected_to root_url
29
+ end
30
+
31
+ test "authorized accesses aren't redirected" do
32
+ get :new, nil, {role: :admin} # Admins can create articles
33
+ assert_response :success
34
+
35
+ get :edit, {id: 1}, {role: :editor} # Editors can edit articles
36
+ assert_response :success
37
+
38
+ get :show, {id: 1}, {role: :user} # Users can view articles
39
+ assert_response :success
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+
3
+ class SecretsControllerTest < ActionController::TestCase
4
+ test "controllers are locked by default" do
5
+ # Test that the "lock_access" call in ApplicationController works properly.
6
+ # There are no access rules in SecretsController but it should be locked anyway.
7
+ get :index, nil, {role: :admin}
8
+ assert_redirected_to root_url
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+
3
+ class StaticControllerTest < ActionController::TestCase
4
+ # Test that the :all key works properly
5
+ test "anyone can do anything" do
6
+ get :home, nil, {role: :admin}
7
+ assert_response :success
8
+
9
+ get :home, nil, {role: :undefined}
10
+ assert_response :success
11
+
12
+ get :home
13
+ assert_response :success
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.