can4 1.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8bf6c29d68e2f8a96d458d8baba14d9116f954a47ca29f32dac886942204006a
4
+ data.tar.gz: 41e2092f6921a89df7552db67c56a633f0f5dc893baab3f5bbbc5cbbbd4b6b2a
5
+ SHA512:
6
+ metadata.gz: b3e57d7a3322f23b0d98b5e7963d284532840c6189be4de60c41ae8c17b7e68504cd48c67c8d67271bdbcfee912ceab1f4b8a921e7dab1f7cb1d669896917779
7
+ data.tar.gz: b5dfcd9ea63efe103f94d479009340fdc87059c207c848561f5ad048e1d29c86ef7fa88dfb5e0fea1ba47c9bf07ea43eb123379e731d7cedd40e18edef60f981
@@ -0,0 +1,2 @@
1
+ .yardoc
2
+ /doc
@@ -0,0 +1,2 @@
1
+ --no-private
2
+ --embed-mixin ClassMethods
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Declare your gem's dependencies in can4.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use a debugger
14
+ # gem 'byebug', group: [:development, :test]
15
+ gem 'rubocop'
16
+ gem 'yard'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019 Liam P. White
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.
@@ -0,0 +1,3 @@
1
+ # Can4
2
+
3
+ An opinionated Ruby ACL module for Rails applications.
@@ -0,0 +1,16 @@
1
+ require_relative 'lib/can4/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'can4'
5
+ s.version = Can4::VERSION
6
+ s.author = 'Liam P. White'
7
+ s.email = 'liamwhite@users.noreply.github.com'
8
+ s.homepage = 'https://github.com/liamwhite/can4'
9
+ s.summary = 'Opinionated ACL framework'
10
+ s.license = 'MIT'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.require_paths = ['lib']
14
+
15
+ s.add_runtime_dependency 'rails'
16
+ end
@@ -0,0 +1,9 @@
1
+ require 'can4/version'
2
+ require 'can4/rule'
3
+ require 'can4/ability'
4
+ require 'can4/controller_additions'
5
+ require 'can4/exceptions'
6
+
7
+ # A simple, fast authorization framework.
8
+ module Can4
9
+ end
@@ -0,0 +1,111 @@
1
+ module Can4
2
+ # Ability class for resources.
3
+ #
4
+ # To define an ability model for your resource, define an ability class in
5
+ # a location of your choosing, and define the actions available to the
6
+ # resource on construction.
7
+ #
8
+ # @example
9
+ # class Ability < Can4::Ability
10
+ # def initialize(user)
11
+ # # Handle unauthenticated users.
12
+ # user ||= User.new
13
+ #
14
+ # if user.admin?
15
+ # # Allow admins to perform any action.
16
+ # allow_anything!
17
+ # else
18
+ # # Will always return true for can?(:read, @comment).
19
+ # can :read, Comment
20
+ #
21
+ # # Will only return true for can?(:read, @private_message)
22
+ # # if the user is allowed to read the private message.
23
+ # can :read, PrivateMessage do |msg|
24
+ # msg.user_id == user.id
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ class Ability
31
+ # Checks whether the object can perform an action on a subject.
32
+ #
33
+ # @overload can?(action, subject)
34
+ # @param action [Symbol] The action, represented as a symbol.
35
+ # @param subject [Object] The subject.
36
+ # @overload can?(action, subject, *args)
37
+ # @param action [Symbol] The action, represented as a symbol.
38
+ # @param subject [Object] The subject.
39
+ # @param args [Object] Splat parameters to an installed block.
40
+ # @return [Boolean] True or false.
41
+ def can?(action, subject, *args)
42
+ lookup_rule(subject).authorized?(action, subject, args)
43
+ end
44
+
45
+ # Inverse of #can?.
46
+ #
47
+ # @see #can?
48
+ def cannot?(*args)
49
+ !can?(*args)
50
+ end
51
+
52
+ # Adds an access-granting rule.
53
+ #
54
+ # @param action [Symbol] The action, represented as a symbol.
55
+ # @param subject [Object] The subject.
56
+ # @param block [Proc] An optional Proc to install for matching.
57
+ def can(action, subject, &block)
58
+ rule_for(subject).add_grant(action, block)
59
+ end
60
+
61
+ # Allows the object to perform any action on any subject.
62
+ # This overrides all #cannot rules.
63
+ def allow_anything!
64
+ instance_eval do
65
+ def can?(*)
66
+ true
67
+ end
68
+
69
+ def cannot?(*)
70
+ false
71
+ end
72
+ end
73
+ end
74
+
75
+ # Checks whether this resource has authorization to perform an action on a
76
+ # particular subject. Raises {Can4::AccessDenied} if it doesn't.
77
+ #
78
+ # @param action [Symbol] The intended action.
79
+ # @param subject [Object] The subject of the action.
80
+ # @raise [AccessDenied] if the object does not have permission.
81
+ def authorize!(action, subject, *args)
82
+ raise AccessDenied if cannot?(action, subject, *args)
83
+ end
84
+
85
+ protected
86
+
87
+ # Subjects hash.
88
+ def subjects
89
+ @subjects ||= {}
90
+ end
91
+
92
+ # Find or create a new rule for the specified subject.
93
+ #
94
+ # @param subject [Object] The subject.
95
+ def rule_for(subject)
96
+ subjects[subject] ||= SubjectRule.new
97
+ end
98
+
99
+ # Lookup a rule for a particular subject.
100
+ #
101
+ # @param subject [Object] The subject.
102
+ def lookup_rule(subject)
103
+ case subject
104
+ when Symbol, Module
105
+ subjects[subject] || NullRule
106
+ else
107
+ subjects[subject.class] || NullRule
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Can4
4
+ # Rails controller additions for Can4.
5
+ #
6
+ # In most cases, it is not necessary to define anything here, as it is
7
+ # included for you automatically when +ActionController::Base+ is defined.
8
+ #
9
+ # However, if your controller resource is not defined using a method named
10
+ # +current_user+, or you use different arguments for your +Ability+
11
+ # constructor, you will need to override the +current_ability+ method in
12
+ # your controller.
13
+ #
14
+ # @example
15
+ # class ApplicationController < ActionController::Base
16
+ # # ...
17
+ #
18
+ # private
19
+ #
20
+ # # This example shows a possible redefinition of current_ability
21
+ # # with a different scope and two constructor arguments.
22
+ # def current_ability
23
+ # @current_ability ||= ::Ability.new(current_admin, request.remote_ip)
24
+ # end
25
+ # end
26
+ #
27
+ module ControllerAdditions
28
+ module ClassMethods
29
+ # Add this to a controller to ensure it performs authorization through an
30
+ # {#authorize!} call.
31
+ #
32
+ # If neither of these authorization methods are called, a
33
+ # {Can4::AuthorizationNotPerformed} exception will be raised.
34
+ #
35
+ # This can be placed in your ApplicationController to ensure all
36
+ # controller actions perform authorization.
37
+ def check_authorization(*args)
38
+ after_action(*args) do |controller|
39
+ next if controller.instance_variable_defined?(:@_authorized)
40
+
41
+ raise AuthorizationNotPerformed,
42
+ 'This action failed to check_authorization because it did not ' \
43
+ 'authorize a resource. Add skip_authorization_check to bypass ' \
44
+ 'this check.'
45
+ end
46
+ end
47
+
48
+ # Call this in the class of a controller to skip the check_authorization
49
+ # behavior on the actions. Arguments are the same as +before_action+.
50
+ def skip_authorization_check(*args)
51
+ before_action(*args) do |controller|
52
+ controller.instance_variable_set(:@_authorized, true)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Raises a {Can4::AccessDenied} exception if the current ability cannot
58
+ # perform the given action. This is usually called in a controller action
59
+ # or +before_action+.
60
+ #
61
+ # You can rescue from the exception in the controller to customize how
62
+ # unauthorized access is displayed.
63
+ #
64
+ # @raise [Can4::AccessDenied]
65
+ # The current ability cannot perform the requested action.
66
+ def authorize!(*args)
67
+ @_authorized = true
68
+ current_ability.authorize!(*args)
69
+ end
70
+
71
+ # Creates and returns the current ability and caches it. If you want to
72
+ # override how the +Ability+ is defined, then this is the place. Simply
73
+ # redefine the method in the controller to change its behavior.
74
+ #
75
+ # Note that it is important to memoize the ability object so it is not
76
+ # recreated every time.
77
+ def current_ability
78
+ @current_ability ||= ::Ability.new(current_user)
79
+ end
80
+
81
+ # Use in the controller or view to check the resources's permission for a
82
+ # given action and object. This simply calls #can? on the current ability.
83
+ #
84
+ # @see Ability#can?
85
+ def can?(*args)
86
+ current_ability.can?(*args)
87
+ end
88
+
89
+ # Convenience method which works the same as {#can?}, but returns the
90
+ # opposite value.
91
+ #
92
+ # @see Ability#cannot?
93
+ def cannot?(*args)
94
+ current_ability.cannot?(*args)
95
+ end
96
+
97
+ def self.included(base)
98
+ base.extend ClassMethods
99
+
100
+ return unless base.respond_to?(:helper_method)
101
+
102
+ base.helper_method :can?, :cannot?, :current_ability
103
+ end
104
+ end
105
+ end
106
+
107
+ if defined?(ActionController::Base)
108
+ ActionController::Base.class_eval do
109
+ include Can4::ControllerAdditions
110
+ end
111
+ end
@@ -0,0 +1,10 @@
1
+ module Can4
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
+ # Raised when a resource fails a call to +authorize!+.
9
+ class AccessDenied < Error; end
10
+ end
@@ -0,0 +1,43 @@
1
+ module Can4
2
+ # Rule class representing actions performable 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
+ # Return whether or not an object can perform a particular action on a
18
+ # subject.
19
+ #
20
+ # @param action [Symbol] The action.
21
+ # @param subject [Object] The subject.
22
+ # @param args [Hash] Variable arguments for more granular matching.
23
+ # @return [Boolean] True or false.
24
+ def authorized?(action, subject, args)
25
+ block = @actions[:manage] || @actions[action]
26
+
27
+ return false unless block
28
+ return true if block == true
29
+
30
+ !!block.call(subject, *args)
31
+ end
32
+ end
33
+
34
+ # Fake rule representing nothing matched a subject when looking up its
35
+ # ability.
36
+ #
37
+ # @!visibility private
38
+ class NullRule
39
+ def self.authorized?(*)
40
+ false
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Can4
2
+ VERSION = '1.0.2'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: can4
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Liam P. White
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email: liamwhite@users.noreply.github.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gitignore"
34
+ - ".yardopts"
35
+ - Gemfile
36
+ - LICENSE
37
+ - README.md
38
+ - can4.gemspec
39
+ - lib/can4.rb
40
+ - lib/can4/ability.rb
41
+ - lib/can4/controller_additions.rb
42
+ - lib/can4/exceptions.rb
43
+ - lib/can4/rule.rb
44
+ - lib/can4/version.rb
45
+ homepage: https://github.com/liamwhite/can4
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.7.6.2
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Opinionated ACL framework
69
+ test_files: []