pragma-policy 0.1.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/.rubocop.yml +12 -8
- data/README.md +17 -87
- data/lib/pragma/policy.rb +4 -1
- data/lib/pragma/policy/base.rb +45 -74
- data/lib/pragma/policy/errors.rb +8 -0
- data/lib/pragma/policy/version.rb +2 -1
- data/pragma-policy.gemspec +17 -14
- metadata +18 -4
- data/lib/pragma/policy/attribute_authorizer.rb +0 -142
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92f9cd653b6f54508780a68f79ccaa83c923780c
|
4
|
+
data.tar.gz: 12813e8504acfcade966bf450a4bd0c3f76db4be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45f33ffbbc876b4a386841e819e8ce85d315039bad4d5644e999d527c88fa97153217f689c99dfb987481a0b6f1483775c7e0bb75ab568a8af4f0fe58caddcea
|
7
|
+
data.tar.gz: f7cd7b953b894075e7fe636c314509c3d8d9972b865312aa7afc758cce36180eda3ed1f30bb710e7d8ea763521e7a9cda0ee401f9786483940b8d5c9c0d740e2
|
data/.rubocop.yml
CHANGED
@@ -15,6 +15,7 @@ AllCops:
|
|
15
15
|
- 'config/**/*'
|
16
16
|
- '**/Rakefile'
|
17
17
|
- '**/Gemfile'
|
18
|
+
- 'pragma-policy.gemspec'
|
18
19
|
|
19
20
|
RSpec/DescribeClass:
|
20
21
|
Exclude:
|
@@ -24,26 +25,26 @@ Style/BlockDelimiters:
|
|
24
25
|
Exclude:
|
25
26
|
- 'spec/**/*'
|
26
27
|
|
27
|
-
|
28
|
+
Layout/AlignParameters:
|
28
29
|
EnforcedStyle: with_fixed_indentation
|
29
30
|
|
30
|
-
|
31
|
+
Layout/ClosingParenthesisIndentation:
|
31
32
|
Enabled: false
|
32
33
|
|
33
34
|
Metrics/LineLength:
|
34
35
|
Max: 100
|
35
36
|
AllowURI: true
|
36
37
|
|
37
|
-
|
38
|
+
Layout/FirstParameterIndentation:
|
38
39
|
Enabled: false
|
39
40
|
|
40
|
-
|
41
|
+
Layout/MultilineMethodCallIndentation:
|
41
42
|
EnforcedStyle: indented
|
42
43
|
|
43
|
-
|
44
|
+
Layout/IndentArray:
|
44
45
|
EnforcedStyle: consistent
|
45
46
|
|
46
|
-
|
47
|
+
Layout/IndentHash:
|
47
48
|
EnforcedStyle: consistent
|
48
49
|
|
49
50
|
Style/SignalException:
|
@@ -53,7 +54,7 @@ Style/BracesAroundHashParameters:
|
|
53
54
|
EnforcedStyle: context_dependent
|
54
55
|
|
55
56
|
Lint/EndAlignment:
|
56
|
-
|
57
|
+
EnforcedStyleAlignWith: variable
|
57
58
|
AutoCorrect: true
|
58
59
|
|
59
60
|
Style/AndOr:
|
@@ -68,7 +69,7 @@ RSpec/NamedSubject:
|
|
68
69
|
RSpec/ExampleLength:
|
69
70
|
Enabled: false
|
70
71
|
|
71
|
-
|
72
|
+
Layout/MultilineMethodCallBraceLayout:
|
72
73
|
Enabled: false
|
73
74
|
|
74
75
|
Metrics/MethodLength:
|
@@ -82,3 +83,6 @@ Metrics/PerceivedComplexity:
|
|
82
83
|
|
83
84
|
Metrics/CyclomaticComplexity:
|
84
85
|
Enabled: false
|
86
|
+
|
87
|
+
Metrics/BlockLength:
|
88
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
Policies provide fine-grained access control for your API resources.
|
9
9
|
|
10
|
-
They are built on top of [
|
10
|
+
They are built on top of the [Pundit](https://github.com/elabs/pundit) gem.
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -36,7 +36,7 @@ To create a policy, simply inherit from `Pragma::Policy::Base`:
|
|
36
36
|
```ruby
|
37
37
|
module API
|
38
38
|
module V1
|
39
|
-
module
|
39
|
+
module Article
|
40
40
|
class Policy < Pragma::Policy::Base
|
41
41
|
end
|
42
42
|
end
|
@@ -44,29 +44,31 @@ module API
|
|
44
44
|
end
|
45
45
|
```
|
46
46
|
|
47
|
-
By default, the policy does not return any objects and forbids all operations.
|
47
|
+
By default, the policy does not return any objects when scoping and forbids all operations.
|
48
48
|
|
49
49
|
You can start customizing your policy by defining a scope and operation predicates:
|
50
50
|
|
51
51
|
```ruby
|
52
52
|
module API
|
53
53
|
module V1
|
54
|
-
module
|
54
|
+
module Article
|
55
55
|
class Policy < Pragma::Policy::Base
|
56
|
-
|
57
|
-
|
56
|
+
class Scope < Pragma::Policy::Base::Scope
|
57
|
+
def resolve
|
58
|
+
scope.where('published = ? OR author_id = ?', true, user.id)
|
59
|
+
end
|
58
60
|
end
|
59
61
|
|
60
62
|
def show?
|
61
|
-
|
63
|
+
record.published? || record.author_id == user.id
|
62
64
|
end
|
63
65
|
|
64
66
|
def update?
|
65
|
-
|
67
|
+
record.author_id == user.id
|
66
68
|
end
|
67
69
|
|
68
70
|
def destroy?
|
69
|
-
|
71
|
+
record.author_id == user.id
|
70
72
|
end
|
71
73
|
end
|
72
74
|
end
|
@@ -76,103 +78,31 @@ end
|
|
76
78
|
|
77
79
|
You are ready to use your policy!
|
78
80
|
|
79
|
-
### Retrieving
|
81
|
+
### Retrieving Records
|
80
82
|
|
81
83
|
To retrieve all the records accessible by a user, use the `.accessible_by` class method:
|
82
84
|
|
83
85
|
```ruby
|
84
|
-
posts = API::V1::
|
86
|
+
posts = API::V1::Article::Policy::Scope.new(user, Article.all).resolve
|
85
87
|
```
|
86
88
|
|
87
|
-
### Authorizing
|
89
|
+
### Authorizing Operations
|
88
90
|
|
89
91
|
To authorize an operation, first instantiate the policy, then use the predicate methods:
|
90
92
|
|
91
93
|
```ruby
|
92
|
-
policy = API::V1::
|
94
|
+
policy = API::V1::Article::Policy.new(user, post)
|
93
95
|
fail 'You cannot update this post!' unless policy.update?
|
94
96
|
```
|
95
97
|
|
96
98
|
Since raising when the operation is forbidden is so common, we provide bang methods a shorthand
|
97
|
-
syntax. `Pragma::Policy::
|
99
|
+
syntax. `Pragma::Policy::NotAuthorizedError` is raised if the predicate method returns `false`:
|
98
100
|
|
99
101
|
```ruby
|
100
|
-
policy = API::V1::
|
102
|
+
policy = API::V1::Article::Policy.new(user, post)
|
101
103
|
policy.update! # raises if the user cannot update the post
|
102
104
|
```
|
103
105
|
|
104
|
-
### Attribute-level authorization
|
105
|
-
|
106
|
-
In some cases, you'll want to prevent a user from updating a certain attribute. You can do that with
|
107
|
-
the `#authorize_attr` method:
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
module API
|
111
|
-
module V1
|
112
|
-
module Post
|
113
|
-
class Policy < Pragma::Policy::Base
|
114
|
-
def update?
|
115
|
-
# admins can do whatever they want
|
116
|
-
return true if user.admin?
|
117
|
-
|
118
|
-
(
|
119
|
-
resource.author_id == user.id &&
|
120
|
-
# regular users cannot change the 'featured' attribute
|
121
|
-
authorize_attr(:featured)
|
122
|
-
)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
```
|
129
|
-
|
130
|
-
You can also allow specific values for an enumerated attribute:
|
131
|
-
|
132
|
-
```ruby
|
133
|
-
module API
|
134
|
-
module V1
|
135
|
-
module Post
|
136
|
-
class Policy < Pragma::Policy::Base
|
137
|
-
def update?
|
138
|
-
# admins can do whatever they want
|
139
|
-
return true if user.admin?
|
140
|
-
|
141
|
-
(
|
142
|
-
resource.author_id == user.id &&
|
143
|
-
# regular users can only set status to 'draft' or 'published'
|
144
|
-
authorize_attr(:status, only: ['draft', 'published'])
|
145
|
-
)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
```
|
152
|
-
|
153
|
-
Or you can invert the condition and specify the forbidden attributes:
|
154
|
-
|
155
|
-
```ruby
|
156
|
-
module API
|
157
|
-
module V1
|
158
|
-
module Post
|
159
|
-
class Policy < Pragma::Policy::Base
|
160
|
-
def update?
|
161
|
-
# admins can do whatever they want
|
162
|
-
return true if user.admin?
|
163
|
-
|
164
|
-
(
|
165
|
-
resource.author_id == user.id &&
|
166
|
-
# regular users cannot set the status to 'rejected'
|
167
|
-
authorize_attr(:status, except: ['rejected'])
|
168
|
-
)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
106
|
## Contributing
|
177
107
|
|
178
108
|
Bug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-policy.
|
data/lib/pragma/policy.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pundit'
|
4
|
+
|
2
5
|
require 'pragma/policy/version'
|
3
6
|
require 'pragma/policy/base'
|
4
|
-
require 'pragma/policy/
|
7
|
+
require 'pragma/policy/errors'
|
5
8
|
|
6
9
|
module Pragma
|
7
10
|
# Fine-grained access control for your API resources.
|
data/lib/pragma/policy/base.rb
CHANGED
@@ -1,41 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Pragma
|
3
4
|
module Policy
|
4
|
-
# This is the base policy class that all your
|
5
|
+
# This is the base policy class that all your record-specific policies should inherit from.
|
5
6
|
#
|
6
7
|
# A policy provides predicate methods for determining whether a user can perform a specific
|
7
|
-
# action on a
|
8
|
+
# action on a record.
|
8
9
|
#
|
9
10
|
# @author Alessandro Desantis
|
10
11
|
#
|
11
12
|
# @abstract Subclass and implement action methods to create a policy.
|
12
13
|
class Base
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
14
|
+
# Authorizes AR scopes and other relations by only returning the records accessible by the
|
15
|
+
# current user. Used, for instance, in index operations.
|
16
|
+
#
|
17
|
+
# @author Alessandro Desantis
|
18
|
+
class Scope
|
19
|
+
# @!attribute [r] user
|
20
|
+
# @return [Object] the user accessing the records
|
21
|
+
#
|
22
|
+
# @!attribute [r] scope
|
23
|
+
# @return [Object] the relation to use as a base
|
24
|
+
attr_reader :user, :scope
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
# Initializes the scope.
|
27
|
+
#
|
28
|
+
# @param user [Object] the user accessing the records
|
29
|
+
# @param scope [Object] the relation to use as a base
|
30
|
+
def initialize(user, scope)
|
31
|
+
@user = user
|
32
|
+
@scope = scope
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the records accessible by the given user.
|
36
|
+
#
|
37
|
+
# @return [Object]
|
38
|
+
#
|
39
|
+
# @abstract Override to implement retrieving the accessible records
|
40
|
+
def resolve
|
41
|
+
fail NotImplementedError
|
42
|
+
end
|
30
43
|
end
|
31
44
|
|
45
|
+
# @!attribute [r] user
|
46
|
+
# @return [Object] the user operating on the record
|
47
|
+
#
|
48
|
+
# @!attribute [r] record
|
49
|
+
# @return [Object] the record being operated on
|
50
|
+
attr_reader :user, :record
|
51
|
+
|
32
52
|
# Initializes the policy.
|
33
53
|
#
|
34
|
-
# @param user [Object] the user operating on the
|
35
|
-
# @param
|
36
|
-
def initialize(user
|
54
|
+
# @param user [Object] the user operating on the record
|
55
|
+
# @param record [Object] the record being operated on
|
56
|
+
def initialize(user, record)
|
37
57
|
@user = user
|
38
|
-
@
|
58
|
+
@record = record
|
39
59
|
end
|
40
60
|
|
41
61
|
# Returns whether the policy responds to the provided missing method.
|
@@ -71,67 +91,18 @@ module Pragma
|
|
71
91
|
# @raise [ForbiddenError] if the user is not authorized to perform the action
|
72
92
|
def authorize(action)
|
73
93
|
unless respond_to?("#{action}?")
|
74
|
-
fail(
|
75
|
-
ArgumentError,
|
76
|
-
"'#{action}' is not a valid action for this policy."
|
77
|
-
)
|
94
|
+
fail(ArgumentError, "'#{action}' is not a valid action for this policy.")
|
78
95
|
end
|
79
96
|
|
80
97
|
return if send("#{action}?")
|
81
98
|
|
82
99
|
fail(
|
83
|
-
|
100
|
+
NotAuthorizedError,
|
84
101
|
user: user,
|
85
102
|
action: action,
|
86
|
-
|
103
|
+
record: record
|
87
104
|
)
|
88
105
|
end
|
89
|
-
|
90
|
-
protected
|
91
|
-
|
92
|
-
# Authorizes a resource attribute.
|
93
|
-
#
|
94
|
-
# @param attribute [Symbol] the name of the attribute
|
95
|
-
# @param options [Hash] options (see {AttributeAuthorizer#authorize} for allowed options)
|
96
|
-
#
|
97
|
-
# @return [Boolean] whether the attribute's value is allowed
|
98
|
-
def authorize_attr(attribute, options = {})
|
99
|
-
AttributeAuthorizer.new(
|
100
|
-
resource: resource,
|
101
|
-
attribute: attribute
|
102
|
-
).authorize(options)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# This error is raised when a user attempts to perform an unauthorized operation on a
|
107
|
-
# resource.
|
108
|
-
#
|
109
|
-
# @author Alessandro Desantis
|
110
|
-
class ForbiddenError < StandardError
|
111
|
-
MESSAGE = "User is not authorized to perform the '%{action}' action on this resource."
|
112
|
-
|
113
|
-
# @!attribtue [r] user
|
114
|
-
# @return [Object] the user operating on the resource
|
115
|
-
#
|
116
|
-
# @!attribute [r] action
|
117
|
-
# @return [Symbol] the attempted action
|
118
|
-
#
|
119
|
-
# @!attribute [r] resource
|
120
|
-
# @return [Object] the resource being operated on
|
121
|
-
attr_reader :user, :action, :resource
|
122
|
-
|
123
|
-
# Initializes the error.
|
124
|
-
#
|
125
|
-
# @param user [Object] the user operating on the resource
|
126
|
-
# @param action [Symbol] the attempted action
|
127
|
-
# @param resource [Object] the resource being operated on
|
128
|
-
def initialize(user:, action:, resource:)
|
129
|
-
@user = user
|
130
|
-
@action = action.to_sym
|
131
|
-
@resource = resource
|
132
|
-
|
133
|
-
super MESSAGE.gsub('%{action}', action.to_s)
|
134
|
-
end
|
135
106
|
end
|
136
107
|
end
|
137
108
|
end
|
data/pragma-policy.gemspec
CHANGED
@@ -1,29 +1,32 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'pragma/policy/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'pragma-policy'
|
8
9
|
spec.version = Pragma::Policy::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
10
|
+
spec.authors = ['Alessandro Desantis']
|
11
|
+
spec.email = ['desa.alessandro@gmail.com']
|
11
12
|
|
12
13
|
spec.summary = 'Fine-grained access control for your API resources.'
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
14
|
+
spec.homepage = 'https://github.com/pragmarb/pragma-policy'
|
15
|
+
spec.license = 'MIT'
|
15
16
|
|
16
17
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
18
|
f.match(%r{^(test|spec|features)/})
|
18
19
|
end
|
19
|
-
spec.bindir =
|
20
|
+
spec.bindir = 'exe'
|
20
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
-
spec.require_paths = [
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_dependency 'pundit', '~> 1.1'
|
22
25
|
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
26
|
+
spec.add_development_dependency 'bundler'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'rubocop'
|
30
|
+
spec.add_development_dependency 'rubocop-rspec'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
29
32
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pragma-policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Desantis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pundit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,8 +126,8 @@ files:
|
|
112
126
|
- bin/console
|
113
127
|
- bin/setup
|
114
128
|
- lib/pragma/policy.rb
|
115
|
-
- lib/pragma/policy/attribute_authorizer.rb
|
116
129
|
- lib/pragma/policy/base.rb
|
130
|
+
- lib/pragma/policy/errors.rb
|
117
131
|
- lib/pragma/policy/version.rb
|
118
132
|
- pragma-policy.gemspec
|
119
133
|
homepage: https://github.com/pragmarb/pragma-policy
|
@@ -136,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
150
|
version: '0'
|
137
151
|
requirements: []
|
138
152
|
rubyforge_project:
|
139
|
-
rubygems_version: 2.
|
153
|
+
rubygems_version: 2.6.13
|
140
154
|
signing_key:
|
141
155
|
specification_version: 4
|
142
156
|
summary: Fine-grained access control for your API resources.
|
@@ -1,142 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module Pragma
|
3
|
-
module Policy
|
4
|
-
# The attribute authorizer provides attribute-level authorization for resource updates.
|
5
|
-
#
|
6
|
-
# It allows you to specify whether a resource attribute can be changed and, if you want, what
|
7
|
-
# values should be allowed.
|
8
|
-
#
|
9
|
-
# If you want, you can subclass this base authorizer to avoid repeating code.
|
10
|
-
#
|
11
|
-
# @author Alessandro Desantis
|
12
|
-
class AttributeAuthorizer
|
13
|
-
# @!attribute [r] resource
|
14
|
-
# @return [ActiveRecord::Base|Reform::Form] the resource being authorized
|
15
|
-
#
|
16
|
-
# @!attribute [r] attribute
|
17
|
-
# @return [Symbol] the attribute being authorized
|
18
|
-
attr_reader :resource, :attribute
|
19
|
-
|
20
|
-
# Initializes the authorizer.
|
21
|
-
#
|
22
|
-
# @param resource [ActiveRecord::Base|Reform::Form] the resource being authorized
|
23
|
-
# @param attribute [Symbol] the attribute being authorized
|
24
|
-
#
|
25
|
-
# @raise [UnknownEngineError] if the resource is not based on Reform or ActiveRecord
|
26
|
-
def initialize(resource:, attribute:)
|
27
|
-
@resource = resource
|
28
|
-
@attribute = attribute
|
29
|
-
|
30
|
-
validate_resource
|
31
|
-
end
|
32
|
-
|
33
|
-
# Returns the old value of the attribute (if any).
|
34
|
-
#
|
35
|
-
# For Reform, this retrieves the current value of the attribute from the model. For
|
36
|
-
# ActiveRecord, uses the +<attribute>_was+ method.
|
37
|
-
#
|
38
|
-
# @return [Object|NilClass]
|
39
|
-
def old_value
|
40
|
-
case resource_engine
|
41
|
-
when :reform
|
42
|
-
resource.model.send(attribute)
|
43
|
-
when :active_record
|
44
|
-
resource.send("#{attribute}_was")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Returns the new (i.e. current) value of the attribute.
|
49
|
-
#
|
50
|
-
# Simply sends the attribute name to the resource.
|
51
|
-
#
|
52
|
-
# @return [Object]
|
53
|
-
def new_value
|
54
|
-
resource.send(attribute)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns whether the attribute has changed, by comparing the new and the old value.
|
58
|
-
#
|
59
|
-
# @return [Boolean]
|
60
|
-
def changed?
|
61
|
-
old_value != new_value
|
62
|
-
end
|
63
|
-
|
64
|
-
# Returns the engine used for the resource being authorized (Reform or ActiveRecord).
|
65
|
-
#
|
66
|
-
# @return [Symbol] +:reform+ or +:active_record+
|
67
|
-
#
|
68
|
-
# @raise [UnknownEngineError] if the engine cannot be detected
|
69
|
-
def resource_engine
|
70
|
-
if defined?(Reform::Form) && resource.is_a?(Reform::Form)
|
71
|
-
:reform
|
72
|
-
elsif defined?(ActiveRecord::Base) && resource.is_a?(ActiveRecord::Base)
|
73
|
-
:active_record
|
74
|
-
else
|
75
|
-
fail UnknownEngineError(resource: resource, attribute: attribute)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Ensures that the attribute was changed according to the provided options.
|
80
|
-
#
|
81
|
-
# When neither +only+ nor +except+ are passed, simply ensures that the attribute was not
|
82
|
-
# changed.
|
83
|
-
#
|
84
|
-
# When +only+ is passed and is not empty, ensures that the value is part of the given array.
|
85
|
-
#
|
86
|
-
# When +except+ is passed and not empty, also ensures that the value is NOT part of the given
|
87
|
-
# array.
|
88
|
-
#
|
89
|
-
# @param options [Hash] a hash of options
|
90
|
-
#
|
91
|
-
# @option options [Array<String>] :only an optional list of allowed values
|
92
|
-
# @option options [Array<String>] :except an optional list of forbidden values
|
93
|
-
#
|
94
|
-
# @return [Boolean] whether the attribute has an authorized value
|
95
|
-
def authorize(options = {})
|
96
|
-
options[:only] = ([options[:only]] || []).flatten.map(&:to_s).reject(&:empty?)
|
97
|
-
options[:except] = ([options[:except]] || []).flatten.map(&:to_s).reject(&:empty?)
|
98
|
-
|
99
|
-
if options[:only].any? && options[:except].any?
|
100
|
-
fail(
|
101
|
-
ArgumentError,
|
102
|
-
'The :only and :except options cannot be used at the same time.'
|
103
|
-
)
|
104
|
-
end
|
105
|
-
|
106
|
-
return true unless changed?
|
107
|
-
|
108
|
-
if options[:only].any?
|
109
|
-
options[:only].include?(new_value.to_s)
|
110
|
-
elsif options[:except].any?
|
111
|
-
!options[:except].include?(new_value.to_s)
|
112
|
-
end || false
|
113
|
-
end
|
114
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
def validate_resource
|
118
|
-
resource_engine
|
119
|
-
end
|
120
|
-
|
121
|
-
# This error when the engine behind a resource cannot be detected for attribute authorization.
|
122
|
-
#
|
123
|
-
# @author Alessanro Desantis
|
124
|
-
class UnknownEngineError < StandardError
|
125
|
-
MESSAGE = 'Attribute authorization only works with Reform forms and ActiveRecord models.'
|
126
|
-
|
127
|
-
# @!attribute [r] resource
|
128
|
-
# @return [Object] the resource
|
129
|
-
attr_reader :resource
|
130
|
-
|
131
|
-
# Initializes the error.
|
132
|
-
#
|
133
|
-
# @param resource [Object] the resource
|
134
|
-
def initialize(resource:)
|
135
|
-
@resource = resource
|
136
|
-
|
137
|
-
super MESSAGE
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|