policy_machine 0.0.1

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