corral_acl 0.9.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +7 -0
  7. data/corral.gemspec +17 -0
  8. data/doc/Ability.html +257 -0
  9. data/doc/Corral/Ability.html +802 -0
  10. data/doc/Corral/AccessDenied.html +146 -0
  11. data/doc/Corral/AuthorizationNotPerformed.html +145 -0
  12. data/doc/Corral/ControllerAdditions/ClassMethods.html +277 -0
  13. data/doc/Corral/ControllerAdditions.html +513 -0
  14. data/doc/Corral/Error.html +141 -0
  15. data/doc/Corral/Generators/AbilityGenerator.html +195 -0
  16. data/doc/Corral/Generators.html +117 -0
  17. data/doc/Corral/NullRule.html +226 -0
  18. data/doc/Corral/SubjectRule.html +406 -0
  19. data/doc/Corral.html +131 -0
  20. data/doc/_index.html +194 -0
  21. data/doc/class_list.html +51 -0
  22. data/doc/css/common.css +1 -0
  23. data/doc/css/full_list.css +58 -0
  24. data/doc/css/style.css +474 -0
  25. data/doc/doc/_index.html +87 -0
  26. data/doc/doc/class_list.html +51 -0
  27. data/doc/doc/css/common.css +1 -0
  28. data/doc/doc/css/full_list.css +58 -0
  29. data/doc/doc/css/style.css +474 -0
  30. data/doc/doc/file_list.html +51 -0
  31. data/doc/doc/frames.html +17 -0
  32. data/doc/doc/index.html +87 -0
  33. data/doc/doc/js/app.js +243 -0
  34. data/doc/doc/js/full_list.js +216 -0
  35. data/doc/doc/js/jquery.js +4 -0
  36. data/doc/doc/method_list.html +51 -0
  37. data/doc/doc/top-level-namespace.html +102 -0
  38. data/doc/file.README.html +82 -0
  39. data/doc/file_list.html +56 -0
  40. data/doc/frames.html +17 -0
  41. data/doc/index.html +82 -0
  42. data/doc/js/app.js +243 -0
  43. data/doc/js/full_list.js +216 -0
  44. data/doc/js/jquery.js +4 -0
  45. data/doc/method_list.html +211 -0
  46. data/doc/top-level-namespace.html +114 -0
  47. data/lib/corral/ability.rb +85 -0
  48. data/lib/corral/controller_additions.rb +76 -0
  49. data/lib/corral/exceptions.rb +10 -0
  50. data/lib/corral/rule.rb +45 -0
  51. data/lib/corral/version.rb +3 -0
  52. data/lib/corral.rb +5 -0
  53. data/lib/generators/corral/ability/USAGE +4 -0
  54. data/lib/generators/corral/ability/ability_generator.rb +11 -0
  55. data/lib/generators/corral/ability/templates/ability.rb +25 -0
  56. data/spec/corral/ability_spec.rb +26 -0
  57. data/spec/spec_helper.rb +97 -0
  58. metadata +101 -0
@@ -0,0 +1,85 @@
1
+ module Corral
2
+ module Ability
3
+ # Check whether the object can perform an action on a subject.
4
+ #
5
+ # @overload can?(action, subject)
6
+ # @param action [Symbol] The action, represented as a symbol.
7
+ # @param subject [Object] The subject.
8
+ # @overload can?(action, subject, args)
9
+ # @param action [Symbol] The action, represented as a symbol.
10
+ # @param subject [Object] The subject.
11
+ # @param args [Hash] Variable arguments for more granular matching.
12
+ # @return [Boolean] True or false.
13
+ def can?(action, subject, *args)
14
+ return true if @allow_anything
15
+ lookup_rule(subject).authorized?(action, subject, args)
16
+ end
17
+
18
+ # Inverse of #can?.
19
+ #
20
+ # @see #can?
21
+ def cannot?(*args)
22
+ not can?(*args)
23
+ end
24
+
25
+ # Adds a granting-access rule.
26
+ #
27
+ # @param action [Symbol] The action, represented as a symbol.
28
+ # @param subject [Object] The subject.
29
+ # @param block [Hash] Variable arguments for more granular matching.
30
+ def can(action, subject, &block)
31
+ rule_for(subject).add_grant(action, block)
32
+ end
33
+
34
+ # Inverse of #can.
35
+ #
36
+ # @see #can
37
+ def cannot(action, subject, &block)
38
+ rule_for(subject).add_deny(action, block)
39
+ end
40
+
41
+ # Allow the object to perform any action on any subject.
42
+ # This overrides any #cannot rules.
43
+ #
44
+ def allow_anything!
45
+ @allow_anything = true
46
+ end
47
+
48
+ # Check whether the object has authorization to perform the
49
+ # action it intends to on the subject. Raise AccessDenied
50
+ # if it doesn't.
51
+ #
52
+ # @param action [Symbol] The intended action.
53
+ # @param subject [Object] The subject of the action.
54
+ # @raise [AccessDenied] if the object does not have permission.
55
+ def authorize!(action, subject, *args)
56
+ raise AccessDenied if cannot?(action, subject, *args)
57
+ end
58
+
59
+ protected
60
+
61
+ # Subjects hash.
62
+ def subjects
63
+ @subjects ||= {}
64
+ end
65
+
66
+ # Find or create a new rule for the specified subject.
67
+ #
68
+ # @param subject [Object] The subject.
69
+ def rule_for(subject)
70
+ subjects[subject] ||= SubjectRule.new
71
+ end
72
+
73
+ # Lookup a rule for a particular subject.
74
+ #
75
+ # @param subject [Object] The subject.
76
+ def lookup_rule(subject)
77
+ case subject
78
+ when Symbol, Class
79
+ r = subjects[subject] || subjects[:all] || NullRule
80
+ else
81
+ subjects[subject.class] || NullRule
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,76 @@
1
+ module Corral
2
+ module ControllerAdditions
3
+ module ClassMethods
4
+ # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
5
+ # If neither of these authorization methods are called, a Corral::AuthorizationNotPerformed exception will be raised.
6
+ # This can be placed in ApplicationController to ensure all controller actions do authorization.
7
+ def check_authorization(options = {})
8
+ self.after_filter(options.slice(:only, :except)) do |controller|
9
+ next if controller.instance_variable_defined?(:@_authorized)
10
+ next if options[:if] && !controller.send(options[:if])
11
+ next if options[:unless] && controller.send(options[:unless])
12
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it did not authorize a resource. Add skip_authorization_check to bypass this check."
13
+ end
14
+ end
15
+
16
+ # Call this in the class of a controller to skip the check_authorization behavior on the actions.
17
+ # Any arguments are passed to the +before_filter+ called.
18
+ def skip_authorization_check(*args)
19
+ self.before_filter(*args) do |controller|
20
+ controller.instance_variable_set(:@_authorized, true)
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.included(base)
26
+ base.extend ClassMethods
27
+ base.helper_method :can?, :cannot?, :current_ability if base.respond_to? :helper_method
28
+ end
29
+
30
+ # Raises a Corral::AccessDenied exception if the current_ability cannot
31
+ # perform the given action. This is usually called in a controller action or
32
+ # before filter to perform the authorization.
33
+ #
34
+ # A :message option can be passed to specify a different message.
35
+ #
36
+ # You can rescue from the exception in the controller to customize how unauthorized
37
+ # access is displayed to the user.
38
+ #
39
+ # See the load_and_authorize_resource method to automatically add the authorize! behavior
40
+ # to the default RESTful actions.
41
+ def authorize!(*args)
42
+ @_authorized = true
43
+ current_ability.authorize!(*args)
44
+ end
45
+
46
+ # Creates and returns the current user's ability and caches it. If you
47
+ # want to override how the Ability is defined then this is the place.
48
+ # Just define the method in the controller to change behavior.
49
+ #
50
+ # Notice it is important to memoize the ability object so it is not
51
+ # recreated every time.
52
+ def current_ability
53
+ @current_ability ||= ::Ability.new(current_user)
54
+ end
55
+
56
+ # Use in the controller or view to check the user's permission for a given action
57
+ # and object.
58
+ #
59
+ # This simply calls "can?" on the current_ability. See Ability#can?.
60
+ def can?(*args)
61
+ current_ability.can?(*args)
62
+ end
63
+
64
+ # Convenience method which works the same as "can?" but returns the opposite value.
65
+ #
66
+ def cannot?(*args)
67
+ current_ability.cannot?(*args)
68
+ end
69
+ end
70
+ end
71
+
72
+ if defined? ActionController::Base
73
+ ActionController::Base.class_eval do
74
+ include Corral::ControllerAdditions
75
+ end
76
+ end
@@ -0,0 +1,10 @@
1
+ module Corral
2
+ # A general exception
3
+ class Error < StandardError; end
4
+
5
+ # Raised when using check_authorization without calling authorize!
6
+ class AuthorizationNotPerformed < Error; end
7
+
8
+ # This error is raised when a user isn't allowed to access a given controller action.
9
+ class AccessDenied < Error; end
10
+ end
@@ -0,0 +1,45 @@
1
+ module Corral
2
+ # Rule class representing actions doable on a subject.
3
+ # @!visibility private
4
+ class SubjectRule
5
+ def initialize
6
+ @actions = {}
7
+ end
8
+
9
+ # Add a granting ACL for a particular action.
10
+ #
11
+ # @param action [symbol] The action.
12
+ # @param block An optional block for granularity.
13
+ def add_grant(action, block)
14
+ @actions[action] = (block || true)
15
+ end
16
+
17
+ # Add a denying ACL for a particular action.
18
+ #
19
+ # @param action [symbol] The action.
20
+ # @param block An optional block for granularity.
21
+ def add_deny(action, block)
22
+ @actions[action] = (block || false)
23
+ end
24
+
25
+ # Return whether or not an object can perform a particular action on a subject
26
+ # @param action [Symbol] The action.
27
+ # @param subject [Object] The subject.
28
+ # @param args [Hash] Variable arguments for more granular matching.
29
+ # @return [Boolean] True or false.
30
+ def authorized?(action, subject, args)
31
+ block = @actions[:manage] || @actions[action]
32
+ return false unless block
33
+ return true if block == true
34
+ return !!block.call(*[subject, *args])
35
+ end
36
+ end
37
+
38
+ # Fake rule representing nothing matched a subject when looking up its ability.
39
+ # @!visibility private
40
+ class NullRule # :nodoc:
41
+ def self.authorized?(*)
42
+ false
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Corral
2
+ VERSION = "0.9.0"
3
+ end
data/lib/corral.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'corral/version'
2
+ require 'corral/rule'
3
+ require 'corral/ability'
4
+ require 'corral/controller_additions'
5
+ require 'corral/exceptions'
@@ -0,0 +1,4 @@
1
+ Description:
2
+ The corral:ability generator creates an Ability class in the models
3
+ directory. You can move this file anywhere you want as long as it
4
+ is in the load path.
@@ -0,0 +1,11 @@
1
+ module Corral
2
+ module Generators
3
+ class AbilityGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ def generate_ability
7
+ copy_file "ability.rb", "app/models/ability.rb"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ class Ability
2
+ include Corral::Ability
3
+
4
+ def initialize(user)
5
+ # Define abilities for the passed in user here. For example:
6
+ #
7
+ # user ||= User.new # guest user (not logged in)
8
+ # if user.admin?
9
+ # allow_anything!
10
+ # else
11
+ # can :read, Post
12
+ # end
13
+ #
14
+ # The first argument to `can` is the action you are giving the user
15
+ # permission to do.
16
+ # If you pass :manage it will apply to every action. Other common actions
17
+ # here are :read, :create, :update and :destroy.
18
+ #
19
+ # The second argument is the resource the user can perform the action on.
20
+ # Pass a Ruby class of the resource.
21
+ #
22
+ # can :update, Article
23
+ #
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+
3
+ describe Corral::Ability do
4
+ before :each do
5
+ (@ability = double).extend Corral::Ability
6
+ end
7
+
8
+ it "is able to do anything" do
9
+ @ability.allow_anything!
10
+ expect(@ability.can?(:manage, String)).to be true
11
+ expect(@ability.cannot?(:manage, String)).to be false
12
+ end
13
+
14
+ it "is able to perform a specific action on everything" do
15
+ @ability.can(:read, :all)
16
+ expect(@ability.can?(:read, String)).to be true
17
+ expect(@ability.can?(:write, String)).to be false
18
+ end
19
+
20
+ it "cannot do something it has been told it cannot do" do
21
+ @ability.can(:read, :all)
22
+ expect(@ability.can?(:read, :this)).to be true
23
+ @ability.cannot(:read, :this)
24
+ expect(@ability.can?(:read, :this)).to be false
25
+ end
26
+ end
@@ -0,0 +1,97 @@
1
+ require 'corral'
2
+ # This file was generated by the `rspec --init` command. Conventionally, all
3
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
4
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
5
+ # this file to always be loaded, without a need to explicitly require it in any
6
+ # files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, consider making
12
+ # a separate helper file that requires the additional dependencies and performs
13
+ # the additional setup, and require it from the spec files that actually need
14
+ # it.
15
+ #
16
+ # The `.rspec` file also contains a few flags that are not defaults but that
17
+ # users commonly want.
18
+ #
19
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20
+ RSpec.configure do |config|
21
+ # rspec-expectations config goes here. You can use an alternate
22
+ # assertion/expectation library such as wrong or the stdlib/minitest
23
+ # assertions if you prefer.
24
+ config.expect_with :rspec do |expectations|
25
+ # This option will default to `true` in RSpec 4. It makes the `description`
26
+ # and `failure_message` of custom matchers include text for helper methods
27
+ # defined using `chain`, e.g.:
28
+ # be_bigger_than(2).and_smaller_than(4).description
29
+ # # => "be bigger than 2 and smaller than 4"
30
+ # ...rather than:
31
+ # # => "be bigger than 2"
32
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
33
+ end
34
+
35
+ # rspec-mocks config goes here. You can use an alternate test double
36
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
37
+ config.mock_with :rspec do |mocks|
38
+ # Prevents you from mocking or stubbing a method that does not exist on
39
+ # a real object. This is generally recommended, and will default to
40
+ # `true` in RSpec 4.
41
+ mocks.verify_partial_doubles = true
42
+ end
43
+
44
+ # The settings below are suggested to provide a good initial experience
45
+ # with RSpec, but feel free to customize to your heart's content.
46
+ =begin
47
+ # These two settings work together to allow you to limit a spec run
48
+ # to individual examples or groups you care about by tagging them with
49
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
50
+ # get run.
51
+ config.filter_run :focus
52
+ config.run_all_when_everything_filtered = true
53
+
54
+ # Allows RSpec to persist some state between runs in order to support
55
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
56
+ # you configure your source control system to ignore this file.
57
+ config.example_status_persistence_file_path = "spec/examples.txt"
58
+
59
+ # Limits the available syntax to the non-monkey patched syntax that is
60
+ # recommended. For more details, see:
61
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
62
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
63
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
64
+ config.disable_monkey_patching!
65
+
66
+ # This setting enables warnings. It's recommended, but in some cases may
67
+ # be too noisy due to issues in dependencies.
68
+ config.warnings = true
69
+
70
+ # Many RSpec users commonly either run the entire suite or an individual
71
+ # file, and it's useful to allow more verbose output when running an
72
+ # individual spec file.
73
+ if config.files_to_run.one?
74
+ # Use the documentation formatter for detailed output,
75
+ # unless a formatter has already been configured
76
+ # (e.g. via a command-line flag).
77
+ config.default_formatter = 'doc'
78
+ end
79
+
80
+ # Print the 10 slowest examples and example groups at the
81
+ # end of the spec run, to help surface which specs are running
82
+ # particularly slow.
83
+ config.profile_examples = 10
84
+
85
+ # Run specs in random order to surface order dependencies. If you find an
86
+ # order dependency and want to debug it, you can fix the order by providing
87
+ # the seed, which is printed after each run.
88
+ # --seed 1234
89
+ config.order = :random
90
+
91
+ # Seed global randomization in this process using the `--seed` CLI option.
92
+ # Setting this allows you to use `--seed` to deterministically reproduce
93
+ # test failures related to randomization by passing the same `--seed` value
94
+ # as the one that triggered the failure.
95
+ Kernel.srand config.seed
96
+ =end
97
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: corral_acl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Arcaire
8
+ - Liam White
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-07-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Yet another authorization solution.
15
+ email: "/dev/null"
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - corral.gemspec
26
+ - doc/Ability.html
27
+ - doc/Corral.html
28
+ - doc/Corral/Ability.html
29
+ - doc/Corral/AccessDenied.html
30
+ - doc/Corral/AuthorizationNotPerformed.html
31
+ - doc/Corral/ControllerAdditions.html
32
+ - doc/Corral/ControllerAdditions/ClassMethods.html
33
+ - doc/Corral/Error.html
34
+ - doc/Corral/Generators.html
35
+ - doc/Corral/Generators/AbilityGenerator.html
36
+ - doc/Corral/NullRule.html
37
+ - doc/Corral/SubjectRule.html
38
+ - doc/_index.html
39
+ - doc/class_list.html
40
+ - doc/css/common.css
41
+ - doc/css/full_list.css
42
+ - doc/css/style.css
43
+ - doc/doc/_index.html
44
+ - doc/doc/class_list.html
45
+ - doc/doc/css/common.css
46
+ - doc/doc/css/full_list.css
47
+ - doc/doc/css/style.css
48
+ - doc/doc/file_list.html
49
+ - doc/doc/frames.html
50
+ - doc/doc/index.html
51
+ - doc/doc/js/app.js
52
+ - doc/doc/js/full_list.js
53
+ - doc/doc/js/jquery.js
54
+ - doc/doc/method_list.html
55
+ - doc/doc/top-level-namespace.html
56
+ - doc/file.README.html
57
+ - doc/file_list.html
58
+ - doc/frames.html
59
+ - doc/index.html
60
+ - doc/js/app.js
61
+ - doc/js/full_list.js
62
+ - doc/js/jquery.js
63
+ - doc/method_list.html
64
+ - doc/top-level-namespace.html
65
+ - lib/corral.rb
66
+ - lib/corral/ability.rb
67
+ - lib/corral/controller_additions.rb
68
+ - lib/corral/exceptions.rb
69
+ - lib/corral/rule.rb
70
+ - lib/corral/version.rb
71
+ - lib/generators/corral/ability/USAGE
72
+ - lib/generators/corral/ability/ability_generator.rb
73
+ - lib/generators/corral/ability/templates/ability.rb
74
+ - spec/corral/ability_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage:
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.5.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Extremely simple authorization solution.
100
+ test_files: []
101
+ has_rdoc: