u-authorization 1.4.0 → 2.0.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.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +29 -0
- data/README.md +94 -0
- data/Rakefile +10 -0
- data/lib/micro/authorization/model.rb +83 -0
- data/lib/micro/authorization/permissions/checker.rb +53 -0
- data/lib/micro/authorization/permissions/model.rb +33 -0
- data/lib/micro/authorization/permissions.rb +20 -0
- data/lib/micro/authorization/policy.rb +35 -0
- data/lib/micro/authorization/utils.rb +11 -0
- data/lib/micro/authorization/version.rb +7 -0
- data/lib/micro/authorization.rb +7 -0
- data/{u-authorization.rb → lib/u-authorization.rb} +1 -1
- data/u-authorization.gemspec +27 -0
- metadata +39 -12
- data/authorization.rb +0 -195
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49ee3c29a92d117c33706e12939a2ced02597514586ae7cf06fe73622d15897f
|
4
|
+
data.tar.gz: b1991fa714603aa32a6288447796460287adcd6220a8485d6e82d6040c4e48cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d480100a329ab41e4707cb124f46d85af1750fc35ee08e615b1cc488714cb8e817394b8b91acf6c965710faf7235a90c13827cf7369c920e974e39d77ad6446b
|
7
|
+
data.tar.gz: d427583d3e8c633138e018d2f2d5c339a615976622a6b224d1e0e370634cc09f08bc5ceb158caff480b9fabaa588aed85340282c58a44ca8bf509d857e181860
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
u-authorization (1.4.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
docile (1.3.2)
|
10
|
+
json (2.2.0)
|
11
|
+
minitest (5.11.3)
|
12
|
+
rake (10.5.0)
|
13
|
+
simplecov (0.17.0)
|
14
|
+
docile (~> 1.1)
|
15
|
+
json (>= 1.8, < 3)
|
16
|
+
simplecov-html (~> 0.10.0)
|
17
|
+
simplecov-html (0.10.2)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
minitest (~> 5.11, >= 5.11.3)
|
24
|
+
rake (~> 10.0)
|
25
|
+
simplecov
|
26
|
+
u-authorization!
|
27
|
+
|
28
|
+
BUNDLED WITH
|
29
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# µ-authorization
|
2
|
+
|
3
|
+
Simple authorization library and role managment for Ruby.
|
4
|
+
|
5
|
+
## Prerequisites
|
6
|
+
|
7
|
+
> Ruby >= 2.2.0
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
```
|
13
|
+
gem 'u-authorization'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
```
|
18
|
+
$ bundle
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
```
|
23
|
+
$ gem install u-authorization
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'ostruct'
|
30
|
+
require 'authorization'
|
31
|
+
|
32
|
+
role = OpenStruct.new(
|
33
|
+
name: 'user',
|
34
|
+
permissions: {
|
35
|
+
'visit' => { 'except' => ['billings'] },
|
36
|
+
'edit_users' => false, # Same as: 'edit_users' => { 'any' => false },
|
37
|
+
'export_as_csv' => { 'except' => ['sales'] }
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
user = OpenStruct.new(id: 1, role: role)
|
42
|
+
|
43
|
+
class SalesPolicy < Micro::Authorization::Policy
|
44
|
+
def edit?(record)
|
45
|
+
user.id == record.user_id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
authorization = Micro::Authorization::Model.build(
|
50
|
+
permissions: user.role.permissions,
|
51
|
+
policies: { default: :sales, sales: SalesPolicy }
|
52
|
+
context: {
|
53
|
+
user: user,
|
54
|
+
permissions: ['dashboard', 'controllers', 'sales', 'index']
|
55
|
+
}
|
56
|
+
)
|
57
|
+
|
58
|
+
# Verifying the permissions for the given context
|
59
|
+
authorization.permissions.to?('visit') #=> true
|
60
|
+
authorization.permissions.to?('export_as_csv') #=> false
|
61
|
+
|
62
|
+
# Verifying permission for a given feature in different contexts
|
63
|
+
has_permission_to = authorization.permissions.to('export_as_csv')
|
64
|
+
has_permission_to.context?('billings') #=> true
|
65
|
+
has_permission_to.context?('sales') #=> false
|
66
|
+
|
67
|
+
charge = OpenStruct.new(id: 2, user_id: user.id)
|
68
|
+
|
69
|
+
# The #to() method fetch and build a policy.
|
70
|
+
authorization.to(:sales).edit?(charge) #=> true
|
71
|
+
|
72
|
+
# :default is the only permitted key to receive
|
73
|
+
# another symbol as value (a policy reference).
|
74
|
+
authorization.to(:default).edit?(charge) #=> true
|
75
|
+
|
76
|
+
# #policy() method has a similar behavior of #to(),
|
77
|
+
# but if there is a policy named as ":default", it will be fetched and instantiated by default.
|
78
|
+
authorization.policy.edit?(charge) #=> true
|
79
|
+
authorization.policy(:sales).edit?(charge) #=> true
|
80
|
+
|
81
|
+
|
82
|
+
# Cloning the authorization changing only its context.
|
83
|
+
new_authorization = authorization.map(context: [
|
84
|
+
'dashboard', 'controllers', 'billings', 'index'
|
85
|
+
])
|
86
|
+
|
87
|
+
new_authorization.permissions.to?('visit') #=> false
|
88
|
+
|
89
|
+
authorization == new_authorization #=> false
|
90
|
+
```
|
91
|
+
|
92
|
+
## Original implementation
|
93
|
+
|
94
|
+
https://gist.github.com/serradura/7d51b979b90609d8601d0f416a9aa373
|
data/Rakefile
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
module Authorization
|
5
|
+
class Model
|
6
|
+
attr_reader :context, :permissions
|
7
|
+
|
8
|
+
def self.build(permissions:, context:, policies: {})
|
9
|
+
permissions_model =
|
10
|
+
Permissions.new(permissions, context: context.delete(:permissions))
|
11
|
+
|
12
|
+
self.new(context, permissions: permissions_model, policies: policies)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(context, permissions:, policies: {})
|
16
|
+
@context = context
|
17
|
+
@policies = {}
|
18
|
+
@policies_cache = {}
|
19
|
+
@permissions = Permissions[permissions]
|
20
|
+
|
21
|
+
add_policies(policies)
|
22
|
+
end
|
23
|
+
|
24
|
+
def map(context: nil, policies: nil)
|
25
|
+
if context.nil? && policies.nil?
|
26
|
+
raise ArgumentError, 'context or policies keywords args must be defined'
|
27
|
+
end
|
28
|
+
|
29
|
+
permissions_context = context || permissions.context
|
30
|
+
|
31
|
+
new_permissions =
|
32
|
+
Permissions.new(permissions.role, context: permissions_context)
|
33
|
+
|
34
|
+
self.class.new(context, permissions: new_permissions,
|
35
|
+
policies: policies || @policies)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_policy(key, policy_klass)
|
39
|
+
raise ArgumentError, 'key must be a Symbol' unless key.is_a?(Symbol)
|
40
|
+
|
41
|
+
default_ref = key == :default && policy_klass.is_a?(Symbol)
|
42
|
+
|
43
|
+
@policies[key] ||= default_ref ? policy_klass : Policy.type(policy_klass)
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_policies(new_policies)
|
49
|
+
unless new_policies.is_a?(Hash)
|
50
|
+
raise ArgumentError, "policies must be a Hash (key => #{Policy.name})"
|
51
|
+
end
|
52
|
+
|
53
|
+
new_policies.each(&method(:add_policy))
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def to(policy_key, subject: nil)
|
59
|
+
policy_klass = fetch_policy(policy_key)
|
60
|
+
|
61
|
+
return policy_klass.new(context, subject, permissions: permissions) if subject
|
62
|
+
|
63
|
+
return @policies_cache[policy_key] if @policies_cache[policy_key]
|
64
|
+
|
65
|
+
policy_klass.new(context, permissions: permissions).tap do |instance|
|
66
|
+
@policies_cache[policy_key] = instance if policy_klass != Policy
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def policy(key = :default, subject: nil)
|
71
|
+
to(key, subject: subject)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def fetch_policy(policy_key)
|
77
|
+
data = @policies[policy_key]
|
78
|
+
value = data || @policies.fetch(:default, Policy)
|
79
|
+
value.is_a?(Symbol) ? fetch_policy(value) : value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Micro
|
2
|
+
module Authorization
|
3
|
+
module Permissions
|
4
|
+
module CheckRole
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(context, role_permissions, required_features)
|
8
|
+
required_features
|
9
|
+
.all? { |feature| has_permission?(context, role_permissions[feature]) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def has_permission?(context, role_permission)
|
15
|
+
return false if role_permission.nil?
|
16
|
+
|
17
|
+
if role_permission == false || role_permission == true
|
18
|
+
role_permission
|
19
|
+
elsif !(any = role_permission['any']).nil?
|
20
|
+
any
|
21
|
+
elsif only = role_permission['only']
|
22
|
+
check_feature_permission(only, context)
|
23
|
+
elsif except = role_permission['except']
|
24
|
+
!check_feature_permission(except, context)
|
25
|
+
else
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_feature_permission(context_values, context)
|
31
|
+
Utils.values_as_downcased_strings(context_values).any? do |context_value|
|
32
|
+
Array(context_value.split('.')).all? { |permission| context.include?(permission) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private_constant :CheckRole
|
38
|
+
|
39
|
+
class Checker
|
40
|
+
attr_reader :required_features
|
41
|
+
|
42
|
+
def initialize(role, features)
|
43
|
+
@role = role
|
44
|
+
@required_features = Utils.values_as_downcased_strings(features)
|
45
|
+
end
|
46
|
+
|
47
|
+
def context?(context)
|
48
|
+
CheckRole.call(context, @role, @required_features)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Micro
|
2
|
+
module Authorization
|
3
|
+
module Permissions
|
4
|
+
class Model
|
5
|
+
attr_reader :role, :context
|
6
|
+
|
7
|
+
def initialize(role_permissions, context:)
|
8
|
+
@role = role_permissions.dup.freeze
|
9
|
+
@cache = {}
|
10
|
+
@context = Utils.values_as_downcased_strings(context).freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def to(features)
|
14
|
+
Permissions::Checker.new(@role, features)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to?(features = nil)
|
18
|
+
has_permission_to = to(features)
|
19
|
+
|
20
|
+
cache_key = has_permission_to.required_features.inspect
|
21
|
+
|
22
|
+
return @cache[cache_key] unless @cache[cache_key].nil?
|
23
|
+
|
24
|
+
@cache[cache_key] = has_permission_to.context?(@context)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_not?(features = nil)
|
28
|
+
!to?(features)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'micro/authorization/permissions/checker'
|
4
|
+
require 'micro/authorization/permissions/model'
|
5
|
+
|
6
|
+
module Micro
|
7
|
+
module Authorization
|
8
|
+
module Permissions
|
9
|
+
def self.[](instance)
|
10
|
+
return instance if instance.is_a?(Permissions::Model)
|
11
|
+
|
12
|
+
raise ArgumentError, "#{instance.inspect} must be a #{self.name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new(role_permissions, context: [])
|
16
|
+
Permissions::Model.new(role_permissions, context: context)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
module Authorization
|
5
|
+
class Policy
|
6
|
+
def self.type(klass)
|
7
|
+
return klass if klass < self
|
8
|
+
|
9
|
+
raise ArgumentError, "policy must be a #{self.name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(context, subject = nil, permissions: nil)
|
13
|
+
@context = context
|
14
|
+
@subject = subject
|
15
|
+
@permissions = permissions
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args, **keyargs, &block)
|
19
|
+
return false if method =~ /\?\z/
|
20
|
+
super(method)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def permissions; @permissions; end
|
26
|
+
def context; @context; end
|
27
|
+
def subject; @subject; end
|
28
|
+
def user
|
29
|
+
@user ||=
|
30
|
+
context.is_a?(Hash) ? context[:user] || context[:current_user] : context
|
31
|
+
end
|
32
|
+
alias_method :current_user, :user
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'micro/authorization/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'u-authorization'
|
7
|
+
spec.version = Micro::Authorization::VERSION
|
8
|
+
spec.authors = ['Rodrigo Serradura']
|
9
|
+
spec.email = ['rodrigo.serradura@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Authorization library and role managment'
|
12
|
+
spec.description = 'Simple authorization library and role managment for Ruby.'
|
13
|
+
spec.homepage = 'https://github.com/serradura/u-authorization'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.required_ruby_version = '>= 2.2.0'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
end
|
metadata
CHANGED
@@ -1,44 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-authorization
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2019-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.0'
|
13
27
|
description: Simple authorization library and role managment for Ruby.
|
14
|
-
email:
|
28
|
+
email:
|
29
|
+
- rodrigo.serradura@gmail.com
|
15
30
|
executables: []
|
16
31
|
extensions: []
|
17
32
|
extra_rdoc_files: []
|
18
33
|
files:
|
19
|
-
-
|
20
|
-
-
|
21
|
-
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lib/micro/authorization.rb
|
40
|
+
- lib/micro/authorization/model.rb
|
41
|
+
- lib/micro/authorization/permissions.rb
|
42
|
+
- lib/micro/authorization/permissions/checker.rb
|
43
|
+
- lib/micro/authorization/permissions/model.rb
|
44
|
+
- lib/micro/authorization/policy.rb
|
45
|
+
- lib/micro/authorization/utils.rb
|
46
|
+
- lib/micro/authorization/version.rb
|
47
|
+
- lib/u-authorization.rb
|
48
|
+
- u-authorization.gemspec
|
49
|
+
homepage: https://github.com/serradura/u-authorization
|
22
50
|
licenses:
|
23
51
|
- MIT
|
24
52
|
metadata: {}
|
25
53
|
post_install_message:
|
26
54
|
rdoc_options: []
|
27
55
|
require_paths:
|
28
|
-
-
|
56
|
+
- lib
|
29
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
30
58
|
requirements:
|
31
59
|
- - ">="
|
32
60
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.2.
|
61
|
+
version: 2.2.0
|
34
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
63
|
requirements:
|
36
64
|
- - ">="
|
37
65
|
- !ruby/object:Gem::Version
|
38
66
|
version: '0'
|
39
67
|
requirements: []
|
40
|
-
|
41
|
-
rubygems_version: 2.7.7
|
68
|
+
rubygems_version: 3.0.1
|
42
69
|
signing_key:
|
43
70
|
specification_version: 4
|
44
71
|
summary: Authorization library and role managment
|
data/authorization.rb
DELETED
@@ -1,195 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Authorization
|
4
|
-
VERSION = '1.4.0'
|
5
|
-
|
6
|
-
MapValuesAsDowncasedStrings = -> (values) do
|
7
|
-
Array(values).map { |value| String(value).downcase }
|
8
|
-
end
|
9
|
-
|
10
|
-
module CheckRolePermission
|
11
|
-
extend self
|
12
|
-
|
13
|
-
def call(context, role_permissions, required_features)
|
14
|
-
required_features
|
15
|
-
.all? { |feature| has_permission?(context, role_permissions[feature]) }
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def has_permission?(context, role_permission)
|
21
|
-
return false if role_permission.nil?
|
22
|
-
|
23
|
-
if role_permission == false || role_permission == true
|
24
|
-
role_permission
|
25
|
-
elsif !(any = role_permission['any']).nil?
|
26
|
-
any
|
27
|
-
elsif only = role_permission['only']
|
28
|
-
check_feature_permission(only, context)
|
29
|
-
elsif except = role_permission['except']
|
30
|
-
!check_feature_permission(except, context)
|
31
|
-
else
|
32
|
-
raise NotImplementedError
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def check_feature_permission(context_values, context)
|
37
|
-
MapValuesAsDowncasedStrings.(context_values).any? do |context_value|
|
38
|
-
Array(context_value.split('.')).all? { |permission| context.include?(permission) }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class FeaturesPermissionChecker
|
44
|
-
attr_reader :required_features
|
45
|
-
|
46
|
-
def initialize(role, features)
|
47
|
-
@role = role
|
48
|
-
@required_features = MapValuesAsDowncasedStrings.(features)
|
49
|
-
end
|
50
|
-
|
51
|
-
def context?(context)
|
52
|
-
CheckRolePermission.call(context, @role, @required_features)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
class Permissions
|
57
|
-
attr_reader :role, :context
|
58
|
-
|
59
|
-
def self.[](instance)
|
60
|
-
return instance if instance.is_a?(Permissions)
|
61
|
-
|
62
|
-
raise ArgumentError, "#{instance.inspect} must be a #{self.name}"
|
63
|
-
end
|
64
|
-
|
65
|
-
def initialize(role_permissions, context: [])
|
66
|
-
@role = role_permissions.dup.freeze
|
67
|
-
@cache = {}
|
68
|
-
@context = MapValuesAsDowncasedStrings.(context).freeze
|
69
|
-
end
|
70
|
-
|
71
|
-
def to(features)
|
72
|
-
FeaturesPermissionChecker.new(@role, features)
|
73
|
-
end
|
74
|
-
|
75
|
-
def to?(features = nil)
|
76
|
-
has_permission_to = to(features)
|
77
|
-
|
78
|
-
cache_key = has_permission_to.required_features.inspect
|
79
|
-
|
80
|
-
return @cache[cache_key] unless @cache[cache_key].nil?
|
81
|
-
|
82
|
-
@cache[cache_key] = has_permission_to.context?(@context)
|
83
|
-
end
|
84
|
-
|
85
|
-
def to_not?(features = nil)
|
86
|
-
!to?(features)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class Policy
|
91
|
-
def self.type(klass)
|
92
|
-
return klass if klass < self
|
93
|
-
|
94
|
-
raise ArgumentError, "policy must be a #{self.name}"
|
95
|
-
end
|
96
|
-
|
97
|
-
def initialize(context, subject = nil, permissions: nil)
|
98
|
-
@context = context
|
99
|
-
@subject = subject
|
100
|
-
@permissions = permissions
|
101
|
-
end
|
102
|
-
|
103
|
-
def method_missing(method, *args, **keyargs, &block)
|
104
|
-
return false if method =~ /\?\z/
|
105
|
-
super(method)
|
106
|
-
end
|
107
|
-
|
108
|
-
private
|
109
|
-
|
110
|
-
def permissions; @permissions; end
|
111
|
-
def context; @context; end
|
112
|
-
def subject; @subject; end
|
113
|
-
def user
|
114
|
-
@user ||=
|
115
|
-
context.is_a?(Hash) ? context[:user] || context[:current_user] : context
|
116
|
-
end
|
117
|
-
alias_method :current_user, :user
|
118
|
-
end
|
119
|
-
|
120
|
-
class Model
|
121
|
-
attr_reader :user, :permissions
|
122
|
-
|
123
|
-
def self.build(user, role, context: [], policies: {})
|
124
|
-
permissions = Permissions.new(role, context: context)
|
125
|
-
|
126
|
-
self.new(user, permissions: permissions, policies: policies)
|
127
|
-
end
|
128
|
-
|
129
|
-
def initialize(user, permissions:, policies: {})
|
130
|
-
@user = user
|
131
|
-
@policies = {}
|
132
|
-
@policies_cache = {}
|
133
|
-
@permissions = Permissions[permissions]
|
134
|
-
|
135
|
-
add_policies(policies)
|
136
|
-
end
|
137
|
-
|
138
|
-
def map(context: nil, policies: nil)
|
139
|
-
if context.nil? && policies.nil?
|
140
|
-
raise ArgumentError, 'context or policies keywords args must be defined'
|
141
|
-
end
|
142
|
-
|
143
|
-
new_permissions =
|
144
|
-
Permissions.new(permissions.role, context: context || @context)
|
145
|
-
|
146
|
-
self.class.new(
|
147
|
-
user, permissions: new_permissions, policies: policies || @policies
|
148
|
-
)
|
149
|
-
end
|
150
|
-
|
151
|
-
def add_policy(key, policy_klass)
|
152
|
-
raise ArgumentError, 'key must be a Symbol' unless key.is_a?(Symbol)
|
153
|
-
|
154
|
-
default_ref = key == :default && policy_klass.is_a?(Symbol)
|
155
|
-
|
156
|
-
@policies[key] ||= default_ref ? policy_klass : Policy.type(policy_klass)
|
157
|
-
|
158
|
-
self
|
159
|
-
end
|
160
|
-
|
161
|
-
def add_policies(new_policies)
|
162
|
-
unless new_policies.is_a?(Hash)
|
163
|
-
raise ArgumentError, "policies must be a Hash (key => #{Policy.name})"
|
164
|
-
end
|
165
|
-
|
166
|
-
new_policies.each(&method(:add_policy))
|
167
|
-
|
168
|
-
self
|
169
|
-
end
|
170
|
-
|
171
|
-
def to(policy_key, subject: nil)
|
172
|
-
policy_klass = fetch_policy(policy_key)
|
173
|
-
|
174
|
-
return policy_klass.new(user, subject, permissions: permissions) if subject
|
175
|
-
|
176
|
-
return @policies_cache[policy_key] if @policies_cache[policy_key]
|
177
|
-
|
178
|
-
policy_klass.new(user, permissions: permissions).tap do |instance|
|
179
|
-
@policies_cache[policy_key] = instance if policy_klass != Policy
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def policy(key = :default, subject: nil)
|
184
|
-
to(key, subject: subject)
|
185
|
-
end
|
186
|
-
|
187
|
-
private
|
188
|
-
|
189
|
-
def fetch_policy(policy_key)
|
190
|
-
data = @policies[policy_key]
|
191
|
-
value = data || @policies.fetch(:default, Policy)
|
192
|
-
value.is_a?(Symbol) ? fetch_policy(value) : value
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|