activity_permission_engine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|