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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.yardopts +2 -0
- data/Gemfile +16 -0
- data/LICENSE +22 -0
- data/README.md +3 -0
- data/can4.gemspec +16 -0
- data/lib/can4.rb +9 -0
- data/lib/can4/ability.rb +111 -0
- data/lib/can4/controller_additions.rb +111 -0
- data/lib/can4/exceptions.rb +10 -0
- data/lib/can4/rule.rb +43 -0
- data/lib/can4/version.rb +3 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.yardopts
ADDED
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.
|
data/README.md
ADDED
data/can4.gemspec
ADDED
@@ -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
|
data/lib/can4.rb
ADDED
data/lib/can4/ability.rb
ADDED
@@ -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
|
data/lib/can4/rule.rb
ADDED
@@ -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
|
data/lib/can4/version.rb
ADDED
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: []
|