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