policy_machine 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.
Files changed (56) hide show
  1. data/CONTRIBUTING.md +35 -0
  2. data/Gemfile +2 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +98 -0
  5. data/lib/generators/policy_machine/policy_machine_generator.rb +13 -0
  6. data/lib/generators/policy_machine/templates/migration.rb +40 -0
  7. data/lib/policy_machine.rb +236 -0
  8. data/lib/policy_machine/association.rb +73 -0
  9. data/lib/policy_machine/policy_element.rb +269 -0
  10. data/lib/policy_machine/version.rb +3 -0
  11. data/lib/policy_machine_storage_adapters/active_record.rb +306 -0
  12. data/lib/policy_machine_storage_adapters/in_memory.rb +266 -0
  13. data/lib/policy_machine_storage_adapters/neography.rb +236 -0
  14. data/lib/policy_machine_storage_adapters/template.rb +169 -0
  15. data/lib/tasks/policy_machine_tasks.rake +4 -0
  16. data/policy_machine.gemspec +23 -0
  17. data/spec/policy_machine/association_spec.rb +61 -0
  18. data/spec/policy_machine/policy_element_spec.rb +20 -0
  19. data/spec/policy_machine_spec.rb +7 -0
  20. data/spec/policy_machine_storage_adapters/active_record_spec.rb +54 -0
  21. data/spec/policy_machine_storage_adapters/in_memory_spec.rb +13 -0
  22. data/spec/policy_machine_storage_adapters/neography_spec.rb +42 -0
  23. data/spec/policy_machine_storage_adapters/template_spec.rb +6 -0
  24. data/spec/spec_helper.rb +24 -0
  25. data/spec/support/neography_helpers.rb +39 -0
  26. data/spec/support/policy_machine_helpers.rb +22 -0
  27. data/spec/support/shared_examples_policy_machine_spec.rb +697 -0
  28. data/spec/support/shared_examples_policy_machine_storage_adapter_spec.rb +278 -0
  29. data/spec/support/shared_examples_storage_adapter_public_methods.rb +20 -0
  30. data/spec/support/storage_adapter_helpers.rb +7 -0
  31. data/test/dummy/Rakefile +7 -0
  32. data/test/dummy/app/controllers/application_controller.rb +3 -0
  33. data/test/dummy/app/helpers/application_helper.rb +2 -0
  34. data/test/dummy/app/models/.gitkeep +0 -0
  35. data/test/dummy/config.ru +4 -0
  36. data/test/dummy/config/application.rb +65 -0
  37. data/test/dummy/config/boot.rb +10 -0
  38. data/test/dummy/config/database.yml +42 -0
  39. data/test/dummy/config/environment.rb +5 -0
  40. data/test/dummy/config/environments/development.rb +37 -0
  41. data/test/dummy/config/environments/test.rb +37 -0
  42. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/test/dummy/config/initializers/inflections.rb +15 -0
  44. data/test/dummy/config/initializers/mime_types.rb +5 -0
  45. data/test/dummy/config/initializers/secret_token.rb +7 -0
  46. data/test/dummy/config/initializers/session_store.rb +8 -0
  47. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/test/dummy/config/routes.rb +58 -0
  49. data/test/dummy/db/migrate/20131015214828_generate_policy_machine.rb +40 -0
  50. data/test/dummy/db/migrate/20131021221759_add_color_to_policy_element.rb +5 -0
  51. data/test/dummy/db/schema.rb +57 -0
  52. data/test/dummy/lib/assets/.gitkeep +0 -0
  53. data/test/dummy/script/rails +6 -0
  54. data/test/policy_machine_test.rb +7 -0
  55. data/test/test_helper.rb +15 -0
  56. metadata +270 -0
@@ -0,0 +1,169 @@
1
+ require 'policy_machine'
2
+
3
+ # This class provides a template for creating your own Policy Machine
4
+ # Storage Adapter. Simply copy this file and implement all public methods.
5
+ # Ensure correctness using the shared examples in
6
+ # 'spec/support/shared_examples_policy_machine_storage_adapter_spec.rb'.
7
+ # Ensure your adapter integrates properly with the policy machine using the shared
8
+ # examples in 'spec/support/shared_examples_policy_machine_spec.rb'.
9
+
10
+ module PolicyMachineStorageAdapter
11
+ class Template
12
+
13
+ ##
14
+ # The following add_* methods store a policy element in the policy machine.
15
+ # The unique_identifier identifies the element within the policy machine.
16
+ # The policy_machine_uuid is the uuid of the containing policy machine.
17
+ # Extra attributes should be persisted as metadata associated with the object.
18
+ # Each method should return the persisted policy element. Persisted policy
19
+ # element objects should respond to each extra attribute key as well as the following methods:
20
+ # * unique_identifier
21
+ # * policy_machine_uuid
22
+ # * persisted
23
+ #
24
+ def add_user(unique_identifier, policy_machine_uuid, extra_attributes = {})
25
+
26
+ end
27
+ def add_user_attribute(unique_identifier, policy_machine_uuid, extra_attributes = {})
28
+
29
+ end
30
+ def add_object(unique_identifier, policy_machine_uuid, extra_attributes = {})
31
+
32
+ end
33
+ def add_object_attribute(unique_identifier, policy_machine_uuid, extra_attributes = {})
34
+
35
+ end
36
+ def add_operation(unique_identifier, policy_machine_uuid, extra_attributes = {})
37
+
38
+ end
39
+ def add_policy_class(unique_identifier, policy_machine_uuid, extra_attributes = {})
40
+
41
+ end
42
+
43
+ ##
44
+ # The following find_* methods should return an array of persisted
45
+ # policy elements of the given type (e.g. user or object_attribute) and extra attributes.
46
+ # If no such persisted policy elements are found, the empty array should
47
+ # be returned.
48
+ #
49
+ def find_all_of_type_user(options = {})
50
+
51
+ end
52
+ def find_all_of_type_user_attribute(options = {})
53
+
54
+ end
55
+ def find_all_of_type_object(options = {})
56
+
57
+ end
58
+ def find_all_of_type_object_attribute(options = {})
59
+
60
+ end
61
+ def find_all_of_type_operation(options = {})
62
+
63
+ end
64
+ def find_all_of_type_policy_class(options = {})
65
+
66
+ end
67
+
68
+ ##
69
+ # Assign src to dst in policy machine.
70
+ # The two policy elements must be persisted policy elements; otherwise the method should raise
71
+ # an ArgumentError.
72
+ # Returns true if the assignment occurred, false otherwise.
73
+ #
74
+ def assign(src, dst)
75
+
76
+ end
77
+
78
+ ##
79
+ # Determine if there is a path from src to dst in the policy machine.
80
+ # The two policy elements must be persisted policy elements; otherwise the method should raise
81
+ # an ArgumentError.
82
+ # Returns true if there is a such a path and false otherwise.
83
+ # Should return true if src == dst
84
+ #
85
+ def connected?(src, dst)
86
+
87
+ end
88
+
89
+ ##
90
+ # Disconnect two policy elements in the machine
91
+ # The two policy elements must be persisted policy elements; otherwise the method should raise
92
+ # an ArgumentError.
93
+ # Returns true if unassignment occurred and false otherwise.
94
+ # Generally, false will be returned if the assignment didn't exist in the PM in the
95
+ # first place.
96
+ #
97
+ def unassign(src, dst)
98
+
99
+ end
100
+
101
+ ##
102
+ # Remove a persisted policy element. This should remove its assignments and
103
+ # associations but must not cascade to any connected policy elements.
104
+ # Returns true if the delete succeeded.
105
+ #
106
+ def delete(element)
107
+
108
+ end
109
+
110
+ ##
111
+ # Update the extra_attributes of a persisted policy element.
112
+ # This should only affect attributes corresponding to the keys passed in.
113
+ # Returns true if the update succeeded or was redundant.
114
+ #
115
+ def update(element, changes_hash)
116
+
117
+ end
118
+
119
+ ##
120
+ # Determine if the given node is in the policy machine or not.
121
+ # Returns true or false accordingly.
122
+ #
123
+ def element_in_machine?(pe)
124
+
125
+ end
126
+
127
+ ##
128
+ # Add the given association to the policy map. If an association between user_attribute
129
+ # and object_attribute already exists, then replace it with that given in the arguments.
130
+ # Returns true if the association was added and false otherwise.
131
+ #
132
+ def add_association(user_attribute, operation_set, object_attribute, policy_machine_uuid)
133
+
134
+ end
135
+
136
+ ##
137
+ # Return an array of all associations in which the given operation is included.
138
+ # Each element of the array should itself be an array in which the first element
139
+ # is the user_attribute member of the association, the second element is a
140
+ # Ruby Set, each element of which is an operation, the third element is the
141
+ # object_attribute member of the association.
142
+ # If no associations are found then the empty array should be returned.
143
+ #
144
+ def associations_with(operation)
145
+
146
+ end
147
+
148
+ ##
149
+ # Return array of all policy classes which contain the given object_attribute (or object).
150
+ # Return empty array if no such policy classes found.
151
+ def policy_classes_for_object_attribute(object_attribute)
152
+
153
+ end
154
+
155
+ ##
156
+ # Return array of all user attributes which contain the given user.
157
+ # Return empty array if no such user attributes are found.
158
+ def user_attributes_for_user(user)
159
+ end
160
+
161
+ ##
162
+ # Execute the passed-in block transactionally: any error raised out of the block causes
163
+ # all the block's changes to be rolled back. Should raise NotImplementedError if the
164
+ # persistence layer does not support this.
165
+ def transaction
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :policy_machine do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "policy_machine"
3
+ s.version = "0.0.1"
4
+ s.summary = "Policy Machine!"
5
+ s.description = "A ruby implementation of the Policy Machine authorization formalism."
6
+ s.authors = ['Matthew Szenher', 'Aaron Weiner']
7
+ s.email = s.authors.map{|name|name.sub(/(.).* (.*)/,'\1\2@mdsol.com')}
8
+ s.homepage = 'https://github.com/mdsol/the_policy_machine'
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
11
+ s.require_paths = ["lib"]
12
+
13
+ s.add_dependency('activesupport')
14
+
15
+ s.add_development_dependency('rspec', '~> 2.13.0')
16
+ s.add_development_dependency('simplecov', '~> 0.7.1')
17
+ s.add_development_dependency('debugger', '~> 1.6.0')
18
+ s.add_development_dependency('neography', '~> 1.1')
19
+ s.add_development_dependency('rails')
20
+ s.add_development_dependency('mysql2')
21
+ s.add_development_dependency('database_cleaner')
22
+
23
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe PM::Association do
4
+
5
+ describe '#create' do
6
+ before do
7
+ @policy_machine = PolicyMachine.new
8
+ @object_attribute = @policy_machine.create_object_attribute('OA name')
9
+ @operation1 = @policy_machine.create_operation('read')
10
+ @operation2 = @policy_machine.create_operation('write')
11
+ @operation_set = Set.new [@operation1, @operation2]
12
+ @user_attribute = @policy_machine.create_user_attribute('UA name')
13
+
14
+ other_pm = PolicyMachine.new
15
+ @other_oa = other_pm.create_object_attribute('UA other')
16
+ @other_op = other_pm.create_operation('delete')
17
+ end
18
+
19
+ it 'raises when first argument is not a user attribute' do
20
+ expect{ PM::Association.create(@object_attribute, @operation_set, @object_attribute, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
21
+ to raise_error(ArgumentError, "user_attribute_pe must be a UserAttribute.")
22
+ end
23
+
24
+ it 'raises when first argument is not in given policy machine' do
25
+ expect{ PM::Association.create(@user_attribute, @operation_set, @object_attribute, "blah", @policy_machine.policy_machine_storage_adapter) }.
26
+ to raise_error(ArgumentError, "user_attribute_pe must be in policy machine with uuid blah")
27
+ end
28
+
29
+ it 'raises when second argument is not a set' do
30
+ expect{ PM::Association.create(@user_attribute, 1, @object_attribute, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
31
+ to raise_error(ArgumentError, "operation_set must be a Set of Operations")
32
+ end
33
+
34
+ it 'raises when second argument is empty set' do
35
+ expect{ PM::Association.create(@user_attribute, Set.new, @object_attribute, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
36
+ to raise_error(ArgumentError, "operation_set must not be empty")
37
+ end
38
+
39
+ it 'raises when second argument is a set in which at least one element is not a PM::Operation' do
40
+ expect{ PM::Association.create(@user_attribute, Set.new([@operation1, 1]), @object_attribute, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
41
+ to raise_error(ArgumentError, "expected 1 to be PM::Operation; got Fixnum")
42
+ end
43
+
44
+ it 'raises when second argument is a set in which at least one element is a PM::Operation which is in a different policy machine' do
45
+ expect{ PM::Association.create(@user_attribute, Set.new([@other_op]), @object_attribute, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
46
+ to raise_error(ArgumentError, "expected #{@other_op.unique_identifier} to be in Policy Machine with uuid #{@policy_machine.uuid}; got #{@other_op.policy_machine_uuid}")
47
+ end
48
+
49
+ it 'raises when third argument is not an object attribute' do
50
+ expect{ PM::Association.create(@user_attribute, @operation_set, "abc", @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
51
+ to raise_error(ArgumentError, "object_attribute_pe must be an ObjectAttribute.")
52
+ end
53
+
54
+ it 'raises when third argument is not in given policy machine' do
55
+ expect{ PM::Association.create(@user_attribute, @operation_set, @other_oa, @policy_machine.uuid, @policy_machine.policy_machine_storage_adapter) }.
56
+ to raise_error(ArgumentError, "object_attribute_pe must be in policy machine with uuid #{@policy_machine.uuid}")
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe PM::PolicyElement do
4
+
5
+ before do
6
+ class GenericPolicyElement < PM::PolicyElement
7
+ def initialize
8
+ end
9
+ end
10
+ end
11
+
12
+ it 'raises if allowed_assignee_classes is not overridden in subclass' do
13
+ expect{ GenericPolicyElement.new.send(:allowed_assignee_classes) }.to raise_error("Must override this method in a subclass")
14
+ end
15
+
16
+ it 'behaves normally when an unknown method is called' do
17
+ expect{ GenericPolicyElement.new.creat }.to raise_error(NoMethodError, /^undefined method `creat' for #<GenericPolicyElement/)
18
+ end
19
+
20
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe PolicyMachine do
4
+ it_behaves_like 'a policy machine' do
5
+ let(:policy_machine) { PolicyMachine.new(:name => 'default PM') }
6
+ end
7
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'policy_machine_storage_adapters/active_record'
3
+ require 'database_cleaner'
4
+
5
+ DatabaseCleaner.strategy = :truncation
6
+
7
+ describe 'ActiveRecord' do
8
+
9
+ before(:each) do
10
+ Rails.cache.clear
11
+ DatabaseCleaner.clean
12
+ end
13
+
14
+ describe PolicyMachineStorageAdapter::ActiveRecord do
15
+ it_behaves_like 'a policy machine storage adapter with required public methods'
16
+ it_behaves_like 'a policy machine storage adapter'
17
+ let(:policy_machine_storage_adapter) { described_class.new }
18
+
19
+ describe 'find_all_of_type' do
20
+
21
+ it 'warns when filtering on an extra attribute' do
22
+ policy_machine_storage_adapter.should_receive(:warn).once
23
+ policy_machine_storage_adapter.find_all_of_type_user(foo: 'bar').should be_empty
24
+ end
25
+
26
+ context 'an extra attribute column has been added to the database' do
27
+
28
+ it 'does not warn' do
29
+ policy_machine_storage_adapter.should_not_receive(:warn)
30
+ policy_machine_storage_adapter.find_all_of_type_user(color: 'red').should be_empty
31
+ end
32
+
33
+ it 'only returns elements that match the hash' do
34
+ policy_machine_storage_adapter.add_object('some_uuid1', 'some_policy_machine_uuid1')
35
+ policy_machine_storage_adapter.add_object('some_uuid2', 'some_policy_machine_uuid1', color: 'red')
36
+ policy_machine_storage_adapter.add_object('some_uuid3', 'some_policy_machine_uuid1', color: 'blue')
37
+ policy_machine_storage_adapter.find_all_of_type_object(color: 'red').should be_one
38
+ policy_machine_storage_adapter.find_all_of_type_object(color: nil).should be_one
39
+ policy_machine_storage_adapter.find_all_of_type_object(color: 'green').should be_none
40
+ policy_machine_storage_adapter.find_all_of_type_object(color: 'blue').map(&:color).should eql(['blue'])
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ describe 'PolicyMachine integration with PolicyMachineStorageAdapter::ActiveRecord' do
50
+ it_behaves_like 'a policy machine' do
51
+ let(:policy_machine) { PolicyMachine.new(:name => 'ActiveRecord PM', :storage_adapter => PolicyMachineStorageAdapter::ActiveRecord) }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'policy_machine_storage_adapters/in_memory'
3
+
4
+ describe PolicyMachineStorageAdapter::InMemory do
5
+ it_behaves_like 'a policy machine storage adapter with required public methods'
6
+ it_behaves_like 'a policy machine storage adapter'
7
+ end
8
+
9
+ describe 'PolicyMachine integration with PolicyMachineStorageAdapter::InMemory' do
10
+ it_behaves_like 'a policy machine' do
11
+ let(:policy_machine) { PolicyMachine.new(:name => 'in memory PM', :storage_adapter => PolicyMachineStorageAdapter::InMemory) }
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'policy_machine_storage_adapters/neography'
3
+
4
+ describe 'Neography' do
5
+ before(:all) do
6
+ stop_neo4j
7
+ reset_neo4j
8
+ start_neo4j
9
+ end
10
+ before(:each) do
11
+ clean_neo4j
12
+ end
13
+ after(:all) do
14
+ stop_neo4j
15
+ end
16
+
17
+ describe PolicyMachineStorageAdapter::Neography do
18
+ it_behaves_like 'a policy machine storage adapter with required public methods'
19
+ it_behaves_like 'a policy machine storage adapter'
20
+ end
21
+
22
+ describe 'PolicyMachine integration with PolicyMachineStorageAdapter::Neography' do
23
+ it_behaves_like 'a policy machine' do
24
+ let(:policy_machine) { PolicyMachine.new(:name => 'neography PM', :storage_adapter => PolicyMachineStorageAdapter::Neography) }
25
+ end
26
+
27
+ describe '#assign' do
28
+ # TODO: storage adapters should be made tolerant to exceptions raised by underlying clients.
29
+ it 'returns false when relationship cannot be created' do
30
+ ::Neography::Relationship.stub(:create).and_return(nil)
31
+ policy_machine_storage_adapter = PolicyMachineStorageAdapter::Neography.new
32
+ src = policy_machine_storage_adapter.add_user('some_uuid1', 'some_policy_machine_uuid1')
33
+ dst = policy_machine_storage_adapter.add_user_attribute('some_uuid2', 'some_policy_machine_uuid1')
34
+ policy_machine_storage_adapter.assign(src, dst).should be_false
35
+ end
36
+ end
37
+ end
38
+ end if neo4j_exists?
39
+
40
+ unless neo4j_exists?
41
+ warn "Integration testing with neo4j requires that neo4j be installed in the gem's root directory"
42
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'policy_machine_storage_adapters/template'
3
+
4
+ describe PolicyMachineStorageAdapter::Template do
5
+ it_behaves_like 'a policy machine storage adapter with required public methods'
6
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../test/test_helper.rb'
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_group 'lib', 'lib'
6
+ add_filter 'spec'
7
+ end
8
+
9
+ require 'rspec'
10
+ require 'debugger'
11
+
12
+ SPEC_DIR = File.expand_path("..", __FILE__)
13
+ lib_dir = File.expand_path("../lib", SPEC_DIR)
14
+
15
+ $LOAD_PATH.unshift(lib_dir)
16
+ $LOAD_PATH.uniq!
17
+
18
+ require 'policy_machine'
19
+
20
+ Dir["./spec/support/**/*.rb"].each {|f| require f}
21
+
22
+ RSpec.configure do |config|
23
+ config.mock_with :rspec
24
+ end
@@ -0,0 +1,39 @@
1
+ # This file contains helper methods to manage a neo4j db via neography, for testing purposes.
2
+
3
+ def neo4j_exists?
4
+ system('test -d neo4j')
5
+ end
6
+
7
+ def start_neo4j
8
+ # Start the server
9
+ # TODO: sometimes the server doesn't start before tests start to run. Fix!
10
+ # TODO: probably have to make a separate connection for test db so as not to wipe out dev data.
11
+ puts "STARTING NEO4J SERVER..."
12
+ %x[neo4j/bin/neo4j start]
13
+ end
14
+
15
+ def stop_neo4j
16
+ puts "STOPPING NEO4J SERVER..."
17
+ %x[neo4j/bin/neo4j stop]
18
+ end
19
+
20
+ def reset_neo4j
21
+ # Reset the database
22
+ puts "RESETTING NEO4J DB..."
23
+ FileUtils.rm_rf("neo4j/data/graph.db")
24
+ FileUtils.mkdir("neo4j/data/graph.db")
25
+
26
+ # Remove log files
27
+ puts "REMOVING NEO4J LOGS..."
28
+ FileUtils.rm_rf("neo4j/data/log")
29
+ FileUtils.mkdir("neo4j/data/log")
30
+ end
31
+
32
+ # Clear all nodes except start node. Should be run before each unit test.
33
+ def clean_neo4j
34
+ neo_connection.execute_query("START n0=node(0),nx=node(*) MATCH n0-[r0?]-(),nx-[rx?]-() WHERE nx <> n0 DELETE r0,rx,nx")
35
+ end
36
+
37
+ def neo_connection
38
+ @neo ||= Neography::Rest.new
39
+ end