activity_permission_engine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +167 -0
  6. data/Rakefile +6 -0
  7. data/activity_permission_engine.gemspec +25 -0
  8. data/lib/activity_permission_engine.rb +47 -0
  9. data/lib/activity_permission_engine/activities_registry.rb +20 -0
  10. data/lib/activity_permission_engine/activity_permissions_registry.rb +59 -0
  11. data/lib/activity_permission_engine/adapters/activity_permissions_registry/memory.rb +39 -0
  12. data/lib/activity_permission_engine/allow_activity.rb +38 -0
  13. data/lib/activity_permission_engine/check_authorization.rb +47 -0
  14. data/lib/activity_permission_engine/disallow_activity.rb +38 -0
  15. data/lib/activity_permission_engine/framework/request.rb +16 -0
  16. data/lib/activity_permission_engine/interface_helpers.rb +31 -0
  17. data/lib/activity_permission_engine/list_activities.rb +27 -0
  18. data/lib/activity_permission_engine/list_activities_permissions.rb +28 -0
  19. data/lib/activity_permission_engine/register_activity.rb +38 -0
  20. data/lib/activity_permission_engine/test_helpers/activity_permissions_registry_test.rb +31 -0
  21. data/lib/activity_permission_engine/unregister_activity.rb +38 -0
  22. data/lib/activity_permission_engine/version.rb +3 -0
  23. data/test/adapters/activities_registry/memory_test.rb +68 -0
  24. data/test/functionnals/activity_permission_engine_test.rb +74 -0
  25. data/test/interface_specifications/allow_activity_test.rb +35 -0
  26. data/test/interface_specifications/check_authorization_test.rb +47 -0
  27. data/test/interface_specifications/disallow_activity_test.rb +33 -0
  28. data/test/interface_specifications/list_activities_permissions_test.rb +26 -0
  29. data/test/interface_specifications/list_activities_test.rb +24 -0
  30. data/test/interface_specifications/register_activity_test.rb +39 -0
  31. data/test/interface_specifications/unregister_activity_test.rb +42 -0
  32. data/test/test_helper.rb +8 -0
  33. metadata +143 -0
@@ -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
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea/
16
+ activity_permission_engine.iml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activity_permission_engine.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |tt|
5
+ tt.test_files = FileList['test/**/*_test.rb']
6
+ end
@@ -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