activity_permission_engine 0.0.1
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 +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +167 -0
- data/Rakefile +6 -0
- data/activity_permission_engine.gemspec +25 -0
- data/lib/activity_permission_engine.rb +47 -0
- data/lib/activity_permission_engine/activities_registry.rb +20 -0
- data/lib/activity_permission_engine/activity_permissions_registry.rb +59 -0
- data/lib/activity_permission_engine/adapters/activity_permissions_registry/memory.rb +39 -0
- data/lib/activity_permission_engine/allow_activity.rb +38 -0
- data/lib/activity_permission_engine/check_authorization.rb +47 -0
- data/lib/activity_permission_engine/disallow_activity.rb +38 -0
- data/lib/activity_permission_engine/framework/request.rb +16 -0
- data/lib/activity_permission_engine/interface_helpers.rb +31 -0
- data/lib/activity_permission_engine/list_activities.rb +27 -0
- data/lib/activity_permission_engine/list_activities_permissions.rb +28 -0
- data/lib/activity_permission_engine/register_activity.rb +38 -0
- data/lib/activity_permission_engine/test_helpers/activity_permissions_registry_test.rb +31 -0
- data/lib/activity_permission_engine/unregister_activity.rb +38 -0
- data/lib/activity_permission_engine/version.rb +3 -0
- data/test/adapters/activities_registry/memory_test.rb +68 -0
- data/test/functionnals/activity_permission_engine_test.rb +74 -0
- data/test/interface_specifications/allow_activity_test.rb +35 -0
- data/test/interface_specifications/check_authorization_test.rb +47 -0
- data/test/interface_specifications/disallow_activity_test.rb +33 -0
- data/test/interface_specifications/list_activities_permissions_test.rb +26 -0
- data/test/interface_specifications/list_activities_test.rb +24 -0
- data/test/interface_specifications/register_activity_test.rb +39 -0
- data/test/interface_specifications/unregister_activity_test.rb +42 -0
- data/test/test_helper.rb +8 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a75028aa4cbffc505ba30e007ae904d27e57e911
|
4
|
+
data.tar.gz: 72824891b3e32831dcd869f5bbab72ba265ce298
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 010ad27442d918988ee0aa4e522d206e03370df9f3645c448d4798c804c4a94280daf18f338c811ffbb6b92f24226749e6249709fd3afcf0fe581214d0f4211a
|
7
|
+
data.tar.gz: 5355706b432b57c95676f1f08949b9e500455e1333bc83184cc4b55318baac7d3565665acd0b4e50dd06a34201c5dacd2cee42af1b389940e6eee50328bd9d8f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Cedric Brancourt, Synbioz
|
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
@@ -0,0 +1,167 @@
|
|
1
|
+
# ActivityPermissionEngine
|
2
|
+
|
3
|
+
This gem provides flexible tooling for managing application permissions
|
4
|
+
|
5
|
+
It allows you to:
|
6
|
+
|
7
|
+
* Set permissions on activities (strings) for some entities like roles.
|
8
|
+
* Check for authorization
|
9
|
+
|
10
|
+
You can use it on its own but, it will fit very well with [Pundit](https://github.com/elabs/pundit) or cancan
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
### Using Bundler
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'activity_permission_engine'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
### System-wide installation
|
27
|
+
|
28
|
+
$ gem install activity_permission_engine
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
### Configure
|
33
|
+
|
34
|
+
You need a persistence adapter. See [activity_permission_engine_redis](https://github.com/synbioz/activity_permission_engine_redis)
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
ActivityPermissionEngine.configure do |config|
|
38
|
+
config.activity_permissions_registry = # Provide the persistence adapter choose from existing ones around the web or create yours
|
39
|
+
config.activities = ['accounting:payments:register','accounting:accounts:read'] # Optional. The list of activities, can be provided at run time.
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
### Roles refs and activities refs
|
44
|
+
|
45
|
+
This library does not make assumptions upon roles except that they should be strings.
|
46
|
+
We do recommend to use business role from the organization chart.
|
47
|
+
|
48
|
+
eg : 'accounting:payment:register' may be allowed to 'accountant' and/or 'sales_executive'
|
49
|
+
'person:update_profile' maybe to 'it_staff:administrator'
|
50
|
+
|
51
|
+
But keep in mind to only use references (not database id). References do not changes and are easy to translate in user readable values.
|
52
|
+
( with I18n if you wish )
|
53
|
+
|
54
|
+
|
55
|
+
### Manage activities
|
56
|
+
|
57
|
+
Activities are part of code and rely on code, they can not persist they are refreshed at each application start.
|
58
|
+
|
59
|
+
eg: 'accounting:payment:register' or 'person:update_profile'
|
60
|
+
|
61
|
+
Activities can be provided at configuration time or run time. It's up to you to choose / mix strategies.
|
62
|
+
|
63
|
+
At run time you may want to create helper method that register the activity once the file is required.
|
64
|
+
At configuration time you'll likely maintain a file that contains all the activity refs.
|
65
|
+
|
66
|
+
|
67
|
+
#### Register an activity
|
68
|
+
|
69
|
+
`ActivityPermissionEngine.register_activity(#activity_ref)`
|
70
|
+
|
71
|
+
Given an activity_ref
|
72
|
+
It will add the activity_ref ( basically a string ) within the activities store.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
ActivityPermissionEngine.register_activity('accounting:payments:register')
|
76
|
+
#=> #<ActivityPermissionEngine::RegisterActivity::Response:0x0055c7cdc90f00 @success=true>
|
77
|
+
```
|
78
|
+
|
79
|
+
#### List activities
|
80
|
+
|
81
|
+
`ActivityPermissionEngine.list_activities`
|
82
|
+
|
83
|
+
Returns the list of activities. You'll need this to provide in your UI the list of activities.
|
84
|
+
The user should then allow role to perform activities.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
ActivityPermissionEngine.list_activities
|
88
|
+
#=> #<ActivityPermissionEngine::ListActivities::Response:0x0055c7cdc78d38 @activity_refs=["accounting:payments:register"]>
|
89
|
+
```
|
90
|
+
|
91
|
+
|
92
|
+
### Activity permissions
|
93
|
+
|
94
|
+
Activities permissions are persisted ( if you set the correct adapter ).
|
95
|
+
This data structure holds the role_ref allowed to perform an activity_ref
|
96
|
+
|
97
|
+
#### Allow role to perform activity
|
98
|
+
|
99
|
+
To allow a role to perform an activity use
|
100
|
+
|
101
|
+
`ActivityPermissionEngine.allow_activity(activity_ref, role_ref)`
|
102
|
+
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
ActivityPermissionEngine.allow_activity('accounting:payments:register', 'accountant')
|
106
|
+
#=> #<ActivityPermissionEngine::AllowActivity::Response:0x0055c7cdc63938 @success=["accountant"]>
|
107
|
+
|
108
|
+
ActivityPermissionEngine.allow_activity('accounting:payments:register', 'sales:executives')
|
109
|
+
#=> #<ActivityPermissionEngine::AllowActivity::Response:0x0055c7cdc42828 @success=["accountant", "sales:executives"]>
|
110
|
+
```
|
111
|
+
|
112
|
+
#### Disallow role to perform activity
|
113
|
+
|
114
|
+
To disallow a role to perform an activity use
|
115
|
+
|
116
|
+
`ActivityPermissionEngine.disallow_activity(activity_ref, role_ref)`
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
ActivityPermissionEngine.disallow_activity('accounting:payments:register', 'sales:executives')
|
120
|
+
#=> #<ActivityPermissionEngine::DisallowActivity::Response:0x0055c7cdc10c88 @success=["accountant"]
|
121
|
+
```
|
122
|
+
|
123
|
+
#### List existing permissions
|
124
|
+
|
125
|
+
A permission exists once you allow a user to perform an activity.
|
126
|
+
|
127
|
+
Use
|
128
|
+
|
129
|
+
`ActivityPermissionEngine.list_activities_permissions` to get a full list of existing activity permissions.
|
130
|
+
|
131
|
+
It returns a Response object that respond to `#activities_permissions` and returns an Array of activities permissions.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
response = ActivityPermissionEngine.list_activities_permissions
|
135
|
+
#=> #<ActivityPermissionEngine::ListActivitiesPermissions::Response:0x0055c7cdbe4278 @activities_permissions=[#<ActivityPermissionEngine::ActivityPermissionsRegistry::ActivityPermission:0x0055c7cdbe42a0 @activity_ref="accounting:payments:register", @role_refs=["accountant"]>]>
|
136
|
+
response.activities_permissions
|
137
|
+
#=> [#<ActivityPermissionEngine::ActivityPermissionsRegistry::ActivityPermission:0x0055c7cdbe42a0 @activity_ref="accounting:payments:register", @role_refs=["accountant"]>]
|
138
|
+
```
|
139
|
+
|
140
|
+
### Authorization check
|
141
|
+
|
142
|
+
To check for authorization you will use `ActivityPermissionEngine.check_authorization(#activity_ref, #role_refs)`
|
143
|
+
The request must respond to role_refs with an array, since most of the time a user can have many roles.
|
144
|
+
It only needs a single matching one to be authorized.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
response = ActivityPermissionEngine.check_authorization('accounting:payments:register',['accountant'])
|
148
|
+
#=> #<ActivityPermissionEngine::CheckAuthorization::Response:0x0055c7cdb95600 @authorized=true>
|
149
|
+
response.authorized?
|
150
|
+
#=> true
|
151
|
+
```
|
152
|
+
|
153
|
+
### Request objects
|
154
|
+
|
155
|
+
You are free to not use the request objects like `ActivityPermissionEngine::AllowActivity::Request`
|
156
|
+
You just need to supply an object that respond to the same methods like a struct.
|
157
|
+
|
158
|
+
But I think it could be wise to use it in order to ensure API compliance and forward compatibility.
|
159
|
+
By the way they are readonly data structures.
|
160
|
+
|
161
|
+
## Contributing
|
162
|
+
|
163
|
+
1. Fork it ( https://github.com/synbioz/activity_permission_engine/fork )
|
164
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
165
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
166
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
167
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'activity_permission_engine/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activity_permission_engine"
|
8
|
+
spec.version = ActivityPermissionEngine::VERSION
|
9
|
+
spec.authors = ["Cedric Brancourt", "Synbioz"]
|
10
|
+
spec.email = ["cedric.brancourt@gmail.com", "opensource@synbioz.com"]
|
11
|
+
spec.summary = %q{Simple library to manage permissions}
|
12
|
+
spec.description = %q{Allow you to map roles to activities and to check for allowance}
|
13
|
+
spec.homepage = "https://github.com/synbioz/activity_permission_engine"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.8"
|
24
|
+
spec.add_development_dependency "simplecov", "~> 0.11"
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'activity_permission_engine/version'
|
2
|
+
require_relative 'activity_permission_engine/interface_helpers'
|
3
|
+
require_relative 'activity_permission_engine/framework/request'
|
4
|
+
require_relative 'activity_permission_engine/register_activity'
|
5
|
+
require_relative 'activity_permission_engine/list_activities'
|
6
|
+
require_relative 'activity_permission_engine/unregister_activity'
|
7
|
+
require_relative 'activity_permission_engine/allow_activity'
|
8
|
+
require_relative 'activity_permission_engine/disallow_activity'
|
9
|
+
require_relative 'activity_permission_engine/check_authorization'
|
10
|
+
require_relative 'activity_permission_engine/activities_registry'
|
11
|
+
require_relative 'activity_permission_engine/adapters/activity_permissions_registry/memory'
|
12
|
+
require_relative 'activity_permission_engine/list_activities_permissions'
|
13
|
+
|
14
|
+
module ActivityPermissionEngine
|
15
|
+
extend InterfaceHelpers
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :configuration
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.configure
|
26
|
+
yield(configuration)
|
27
|
+
end
|
28
|
+
|
29
|
+
class Configuration
|
30
|
+
def initialize(options={})
|
31
|
+
@activity_permissions_registry = options.fetch(:activity_permissions_registry, Defaults.activities_permissions_registry)
|
32
|
+
@activities = options.fetch(:activities, [])
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :activity_permissions_registry, :activities
|
36
|
+
|
37
|
+
def activities_registry
|
38
|
+
@activities_registry ||= ActivitiesRegistry.new(activities)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Defaults
|
43
|
+
def self.activities_permissions_registry
|
44
|
+
Adapters::ActivityPermissionsRegistry::Memory.new
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActivityPermissionEngine
|
2
|
+
class ActivitiesRegistry
|
3
|
+
def initialize(activities)
|
4
|
+
@activities = activities
|
5
|
+
end
|
6
|
+
|
7
|
+
def all
|
8
|
+
activities
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(activity_refs)
|
12
|
+
activities << activity_refs
|
13
|
+
activities.uniq!
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_accessor(:activities)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActivityPermissionEngine
|
2
|
+
module ActivityPermissionsRegistry
|
3
|
+
# Include this module in adapters to provide expected behavior
|
4
|
+
module Interface
|
5
|
+
|
6
|
+
# @param [String] activity_ref
|
7
|
+
# @return [Boolean] true if added or false
|
8
|
+
def add(activity_ref)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Array(ActivityPermission)]
|
13
|
+
def all
|
14
|
+
get_all_activities.map { |activity| ActivityPermission.new(activity[:activity_ref], activity[:role_refs])}
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [String] activity_ref
|
18
|
+
# @return [Boolean] true if deleted or false
|
19
|
+
def del(activity_ref)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] activity_ref
|
24
|
+
# @param [Array(String)] role_ref
|
25
|
+
# @return [Boolean] true if added or false
|
26
|
+
def add_role(activity_ref, role_ref)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [String] activity_ref
|
31
|
+
# @return [ActivityPermission] the found activity or false
|
32
|
+
def find_by_activity_ref(activity_ref)
|
33
|
+
activity = get_activity_by_ref(activity_ref)
|
34
|
+
activity && ActivityPermission.new(activity[:activity_ref], activity[:role_refs])
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @return [Array(Hash{Symbol => String})]
|
40
|
+
def get_all_activities
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Hash{Symbol => String}] {activity_ref: '', role_refs: ''}
|
45
|
+
def get_activity_by_ref(activity_ref)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ActivityPermission
|
51
|
+
def initialize(activity_ref, role_refs)
|
52
|
+
@activity_ref = activity_ref
|
53
|
+
@role_refs = role_refs
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader(:activity_ref, :role_refs)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../../activity_permissions_registry'
|
2
|
+
|
3
|
+
module ActivityPermissionEngine
|
4
|
+
module Adapters
|
5
|
+
module ActivityPermissionsRegistry
|
6
|
+
class Memory
|
7
|
+
include ActivityPermissionEngine::ActivityPermissionsRegistry::Interface
|
8
|
+
|
9
|
+
def initialize(store = {})
|
10
|
+
@store = store
|
11
|
+
@store.default = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def del(activity_ref)
|
15
|
+
store.delete(activity_ref)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_role(activity_ref, role_ref)
|
19
|
+
store.has_key?(activity_ref) ? store[activity_ref].push(role_ref) : store[activity_ref] = [role_ref]
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_role(activity_ref, role_ref)
|
23
|
+
store[activity_ref] = store[activity_ref] - [role_ref]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
attr_reader(:store)
|
28
|
+
|
29
|
+
def get_activity_by_ref(activity_ref)
|
30
|
+
store.has_key?(activity_ref) ? {activity_ref: activity_ref, role_refs: store[activity_ref]} : false
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_all_activities
|
34
|
+
store.map {|k,v| {activity_ref: k, role_refs: v} }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActivityPermissionEngine
|
2
|
+
class AllowActivity
|
3
|
+
def initialize(request, activities_registry = ActivityPermissionEngine.configuration.activity_permissions_registry)
|
4
|
+
@request = request
|
5
|
+
@activities_registry = activities_registry
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def call
|
10
|
+
Response.new(activities_registry.add_role(request.activity_ref, request.role_ref))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
attr_reader(:request, :activities_registry)
|
15
|
+
|
16
|
+
class Request
|
17
|
+
include Framework::Request
|
18
|
+
def initialize(activity_ref, role_ref)
|
19
|
+
@activity_ref = activity_ref
|
20
|
+
@role_ref = role_ref
|
21
|
+
end
|
22
|
+
attr_reader(:activity_ref, :role_ref)
|
23
|
+
end
|
24
|
+
|
25
|
+
class Response
|
26
|
+
def initialize(success)
|
27
|
+
@success = success
|
28
|
+
end
|
29
|
+
|
30
|
+
def success?
|
31
|
+
success
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
attr_reader(:success)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|