permission_policy 0.0.6 → 0.1.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/CHANGELOG.md +4 -0
- data/README.md +22 -0
- data/lib/permission_policy/authorization.rb +5 -2
- data/lib/permission_policy/configuration.rb +1 -35
- data/lib/permission_policy/controller_additions.rb +9 -5
- data/lib/permission_policy/version.rb +1 -1
- data/spec/permission_policy/authorization_spec.rb +3 -7
- data/spec/permission_policy/configuration_spec.rb +0 -9
- data/spec/permission_policy/controller_additions_spec.rb +94 -14
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf58d8db5cb145457f2477e95784d7083d1ae3a9
|
4
|
+
data.tar.gz: 6186f34bf9b8171db1b061123e37d78c975c25b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a01d8c7b01188ab26ad30d5ee23ea8e7c7f4c25c71f88a2bd5310849d09c6a3c2da8f30b12d091cd6fd0ca62f0133c435ad019c30dc58df931314ae339f481fe
|
7
|
+
data.tar.gz: 14fdc5e33244dd581c6f48cf940b24e1bec4f1297e526246c7a81457570c1b2b5625098256d3d2c58d76b24ee8b9ad0810313485f5921beb0debaff7d8d59f9d
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,21 @@
|
|
6
6
|
* dependencies
|
7
7
|
|
8
8
|
|
9
|
+
## Authorization is hard
|
10
|
+
|
11
|
+
There are plenty of diferent solutions to this problem.
|
12
|
+
Because the requirements can be very difficult and as the system grows the problems become even worse. Exspecially when you need to support change.
|
13
|
+
The hardest part may be thight coupling and issues with encapsulation through the whole application.
|
14
|
+
To avoid repetitive and grwoing if/else branches you want a single responsible system.
|
15
|
+
|
16
|
+
In general, the permission policy defines and controls whether a current **object** (usually the user) can perform a specific **action** on a given **subject**. This decision is commonly based on one or more
|
17
|
+
factors:
|
18
|
+
|
19
|
+
* role
|
20
|
+
* type (or a set of attributes)
|
21
|
+
* enabled features / areas of the application
|
22
|
+
|
23
|
+
|
9
24
|
## Installation
|
10
25
|
|
11
26
|
Add this line to your application's Gemfile:
|
@@ -122,6 +137,13 @@ You can extend the permission policy by:
|
|
122
137
|
- implement new permission strategies, which help the policy to find the 'decider'
|
123
138
|
|
124
139
|
|
140
|
+
## possible ideas/improvements:
|
141
|
+
|
142
|
+
- support skip_verify_authorization only:/except: [:actions]
|
143
|
+
- register features and roles in the policy
|
144
|
+
- raise NoSuchFeature
|
145
|
+
- get available "permissions" for role/subject/feature
|
146
|
+
|
125
147
|
## Contributing
|
126
148
|
|
127
149
|
1. Fork it ( https://github.com/[my-github-username]/permission_policy/fork )
|
@@ -4,8 +4,9 @@ module PermissionPolicy
|
|
4
4
|
|
5
5
|
def initialize(context)
|
6
6
|
@preconditions = []
|
7
|
+
@context = context
|
7
8
|
|
8
|
-
|
9
|
+
context.authorization_preconditions.each do |precondition|
|
9
10
|
set! precondition, context.public_send(precondition)
|
10
11
|
@preconditions << precondition
|
11
12
|
end
|
@@ -40,7 +41,9 @@ module PermissionPolicy
|
|
40
41
|
|
41
42
|
# Finds the matching strategy which can decide if the action is allowed by lazy checking
|
42
43
|
def strategy_for(*args)
|
43
|
-
|
44
|
+
@context.authorization_strategies.lazy.map do |klass|
|
45
|
+
Strategies.const_get(klass).new(self, *args)
|
46
|
+
end.find(&:match?)
|
44
47
|
end
|
45
48
|
|
46
49
|
def set!(var, value)
|
@@ -1,38 +1,4 @@
|
|
1
1
|
module PermissionPolicy
|
2
|
-
class Configuration
|
3
|
-
def preconditions
|
4
|
-
precondition_attributes || [:current_user]
|
5
|
-
end
|
6
|
-
|
7
|
-
def strategies
|
8
|
-
strategy_order || [:UnknownStrategy]
|
9
|
-
end
|
10
|
-
|
11
|
-
def verification
|
12
|
-
verify_authorization || false
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class << self
|
17
|
-
attr_accessor :configuration
|
18
|
-
|
19
|
-
extend Forwardable
|
20
|
-
delegate [:preconditions, :strategies, :verification] => :config
|
21
|
-
|
22
|
-
def configure
|
23
|
-
yield(config)
|
24
|
-
end
|
25
|
-
|
26
|
-
def config
|
27
|
-
self.configuration ||= Configuration.new
|
28
|
-
end
|
29
|
-
|
30
|
-
def authorize_with(*args)
|
31
|
-
configure { |c| c.precondition_attributes = *args }
|
32
|
-
end
|
33
|
-
|
34
|
-
def verify_authorization!(setting)
|
35
|
-
configure { |c| c.verify_authorization = setting }
|
36
|
-
end
|
2
|
+
class Configuration
|
37
3
|
end
|
38
4
|
end
|
@@ -3,16 +3,20 @@ require 'active_support/concern'
|
|
3
3
|
module PermissionPolicy
|
4
4
|
module ControllerAdditions
|
5
5
|
module ClassMethods
|
6
|
-
def authorize_with(*
|
7
|
-
|
6
|
+
def authorize_with(*preconditions)
|
7
|
+
define_method('authorization_preconditions') { preconditions }
|
8
8
|
end
|
9
9
|
|
10
10
|
def verify_authorization!
|
11
|
-
|
11
|
+
define_method('authorization_verification?') { true }
|
12
12
|
end
|
13
13
|
|
14
14
|
def skip_verify_authorization
|
15
|
-
|
15
|
+
define_method('authorization_verification?') { false }
|
16
|
+
end
|
17
|
+
|
18
|
+
def authorization_strategies(*strategies)
|
19
|
+
define_method('authorization_strategies') { strategies }
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
@@ -23,7 +27,7 @@ module PermissionPolicy
|
|
23
27
|
helper_method :allowed?
|
24
28
|
delegate :allowed?, to: :permission_policy
|
25
29
|
delegate :authorize!, to: :permission_policy
|
26
|
-
after_action -> { verify_authorization if
|
30
|
+
after_action -> { verify_authorization if authorization_verification? }
|
27
31
|
end
|
28
32
|
|
29
33
|
def permission_policy
|
@@ -1,13 +1,9 @@
|
|
1
1
|
module PermissionPolicy
|
2
2
|
RSpec.describe 'Authorization' do
|
3
|
-
|
4
|
-
PermissionPolicy.authorize_with :my_user, :my_account
|
5
|
-
end
|
6
|
-
|
7
|
-
subject { PermissionPolicy::Authorization.new(context) }
|
3
|
+
subject { PermissionPolicy::Authorization.new(controller_context) }
|
8
4
|
|
9
5
|
context 'valid' do
|
10
|
-
let(:
|
6
|
+
let(:controller_context) { double('context', authorization_preconditions: [:my_user, :my_account], my_user: 'foo', my_account: 'bar') }
|
11
7
|
|
12
8
|
it 'sets attribute readers for each precondition' do
|
13
9
|
expect(subject.my_user).to eq('foo')
|
@@ -20,7 +16,7 @@ module PermissionPolicy
|
|
20
16
|
end
|
21
17
|
|
22
18
|
context 'invalid' do
|
23
|
-
let(:
|
19
|
+
let(:controller_context) { double('context', authorization_preconditions: [:my_user, :my_account], my_user: nil, my_account: nil) }
|
24
20
|
|
25
21
|
it 'raises missing precondition error' do
|
26
22
|
expect { subject }.to raise_error('missing precondition: my_user')
|
@@ -1,13 +1,4 @@
|
|
1
1
|
module PermissionPolicy
|
2
2
|
RSpec.describe Configuration do
|
3
|
-
before { PermissionPolicy.configuration = nil }
|
4
|
-
|
5
|
-
it 'has a default precondition' do
|
6
|
-
expect(subject.preconditions).to eq([:current_user])
|
7
|
-
end
|
8
|
-
|
9
|
-
it 'has a default strategy' do
|
10
|
-
expect(subject.strategies).to eq([:UnknownStrategy])
|
11
|
-
end
|
12
3
|
end
|
13
4
|
end
|
@@ -6,38 +6,118 @@ class MetalTestController < ActionController::Metal
|
|
6
6
|
include PermissionPolicy::ControllerAdditions::InstanceMethods
|
7
7
|
extend PermissionPolicy::ControllerAdditions::ClassMethods
|
8
8
|
|
9
|
+
authorize_with :foo, :bar
|
10
|
+
verify_authorization!
|
11
|
+
authorization_strategies :TestStrategy, :UnknownStrategy
|
12
|
+
|
9
13
|
def foo
|
14
|
+
'foo'
|
10
15
|
end
|
11
16
|
|
12
17
|
def bar
|
18
|
+
'bar'
|
19
|
+
end
|
20
|
+
|
21
|
+
def baz
|
22
|
+
'baz'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TestController < MetalTestController
|
27
|
+
def not_authorized_action
|
28
|
+
'you will never see me'
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_allowed_action
|
32
|
+
authorize! :something
|
33
|
+
'you also wont see me, but get a different error'
|
34
|
+
end
|
35
|
+
|
36
|
+
def allowed_action
|
37
|
+
authorize! :view, subject: 'some_subject'
|
38
|
+
'ok go ahead'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class TestStrategy < PermissionPolicy::Strategies::BaseStrategy
|
43
|
+
def match?
|
44
|
+
options[:subject] == 'some_subject'
|
45
|
+
end
|
46
|
+
|
47
|
+
def allowed?
|
48
|
+
{ view: true, manage: false }[action]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class SkipTestController < MetalTestController
|
53
|
+
authorize_with :baz
|
54
|
+
skip_verify_authorization
|
55
|
+
|
56
|
+
def skipped_authorization
|
57
|
+
'ohai'
|
13
58
|
end
|
14
59
|
end
|
15
60
|
|
16
61
|
|
17
62
|
module PermissionPolicy
|
18
63
|
RSpec.describe 'ControllerAdditions' do
|
19
|
-
subject { MetalTestController.new }
|
20
64
|
|
21
|
-
context '
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
65
|
+
context 'instance_methods' do
|
66
|
+
subject { MetalTestController.new }
|
67
|
+
|
68
|
+
describe 'authorization' do
|
69
|
+
it 'is available through #permission_policy' do
|
70
|
+
expect(subject.permission_policy).to be_kind_of(PermissionPolicy::Authorization)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'is initialized with precondition foo' do
|
74
|
+
expect(subject.permission_policy.foo).to eq('foo')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'is initialized with precondition bar' do
|
78
|
+
expect(subject.permission_policy.bar).to eq('bar')
|
79
|
+
end
|
26
80
|
end
|
27
81
|
|
28
|
-
|
29
|
-
|
82
|
+
describe 'helpers' do
|
83
|
+
it { is_expected.to respond_to :authorize! }
|
84
|
+
it { is_expected.to respond_to :allowed? }
|
30
85
|
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'class_methods' do
|
89
|
+
subject { TestController.new }
|
90
|
+
|
91
|
+
describe '#verify_authorization!' do
|
92
|
+
context 'no call to #authorize!' do
|
93
|
+
it 'is not verified' do
|
94
|
+
expect { subject.process_action :not_authorized_action }.to raise_error PermissionPolicy::NotVerified
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'with #authorize! but not allowed' do
|
99
|
+
it 'is still not allowed' do
|
100
|
+
expect { subject.process_action :not_allowed_action }.to raise_error PermissionPolicy::NotAllowed
|
101
|
+
end
|
102
|
+
end
|
31
103
|
|
32
|
-
|
33
|
-
|
34
|
-
|
104
|
+
context '#authorized! and allowed' do
|
105
|
+
it 'is allowed' do
|
106
|
+
expect(subject.process_action :allowed_action).to eq('ok go ahead')
|
107
|
+
end
|
108
|
+
end
|
35
109
|
end
|
36
110
|
end
|
37
111
|
|
38
|
-
|
39
|
-
|
40
|
-
|
112
|
+
describe '#skip_verify_authorization' do
|
113
|
+
subject { SkipTestController.new }
|
114
|
+
|
115
|
+
context 'with :only option' do
|
116
|
+
it 'skips this action' do
|
117
|
+
expect(subject.process_action :skipped_authorization).to eq('ohai')
|
118
|
+
end
|
119
|
+
end
|
41
120
|
end
|
121
|
+
|
42
122
|
end
|
43
123
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: permission_policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marco Schaden
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-02-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|