policy_machine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING.md +35 -0
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/README.md +98 -0
- data/lib/generators/policy_machine/policy_machine_generator.rb +13 -0
- data/lib/generators/policy_machine/templates/migration.rb +40 -0
- data/lib/policy_machine.rb +236 -0
- data/lib/policy_machine/association.rb +73 -0
- data/lib/policy_machine/policy_element.rb +269 -0
- data/lib/policy_machine/version.rb +3 -0
- data/lib/policy_machine_storage_adapters/active_record.rb +306 -0
- data/lib/policy_machine_storage_adapters/in_memory.rb +266 -0
- data/lib/policy_machine_storage_adapters/neography.rb +236 -0
- data/lib/policy_machine_storage_adapters/template.rb +169 -0
- data/lib/tasks/policy_machine_tasks.rake +4 -0
- data/policy_machine.gemspec +23 -0
- data/spec/policy_machine/association_spec.rb +61 -0
- data/spec/policy_machine/policy_element_spec.rb +20 -0
- data/spec/policy_machine_spec.rb +7 -0
- data/spec/policy_machine_storage_adapters/active_record_spec.rb +54 -0
- data/spec/policy_machine_storage_adapters/in_memory_spec.rb +13 -0
- data/spec/policy_machine_storage_adapters/neography_spec.rb +42 -0
- data/spec/policy_machine_storage_adapters/template_spec.rb +6 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/neography_helpers.rb +39 -0
- data/spec/support/policy_machine_helpers.rb +22 -0
- data/spec/support/shared_examples_policy_machine_spec.rb +697 -0
- data/spec/support/shared_examples_policy_machine_storage_adapter_spec.rb +278 -0
- data/spec/support/shared_examples_storage_adapter_public_methods.rb +20 -0
- data/spec/support/storage_adapter_helpers.rb +7 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +65 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +42 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/db/migrate/20131015214828_generate_policy_machine.rb +40 -0
- data/test/dummy/db/migrate/20131021221759_add_color_to_policy_element.rb +5 -0
- data/test/dummy/db/schema.rb +57 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/policy_machine_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- metadata +270 -0
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
If you find a bug:
|
4
|
+
|
5
|
+
* Check the "GitHub issue tracker" to see if anyone else has reported issue.
|
6
|
+
* If you don't see anything, create an issue with information on how to reproduce it.
|
7
|
+
|
8
|
+
If you want to contribute an enhancement or a fix:
|
9
|
+
|
10
|
+
* Fork the project on GitHub.
|
11
|
+
* bundle install
|
12
|
+
* Make your changes with tests.
|
13
|
+
* Run all automated tests to see if your change broke anything or is providing anything less than 100% code coverage (see below).
|
14
|
+
* Commit the changes without making changes to any other files that aren't related to your enhancement or fix.
|
15
|
+
* Send a pull request.
|
16
|
+
|
17
|
+
## Running Automated Tests
|
18
|
+
|
19
|
+
Run all rspec with:
|
20
|
+
|
21
|
+
```
|
22
|
+
[bundle exec] rspec
|
23
|
+
```
|
24
|
+
|
25
|
+
Simplecov code coverage is generated automatically. Any changes you make to this repository should
|
26
|
+
ensure that code coverage remains at 100%. **No pull request will be merged unless proof is given
|
27
|
+
(i.e. a PR comment) that code coverage remains at 100% in the PR branch.**
|
28
|
+
|
29
|
+
## Making Your Own Policy Machine Storage Adapter
|
30
|
+
|
31
|
+
A template storage adapter is provided in `lib/policy_machine_storage_adapters/template.rb`. Copy this
|
32
|
+
storage adapter as a starting point for making your own; implement all methods contained therein.
|
33
|
+
|
34
|
+
To test your storage adapter, adapt the tests in either `spec/policy_machine_storage_adapters/in_memory_spec.rb` or
|
35
|
+
`spec/policy_machine_storage_adapters/neography_spec.rb`.
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
policy_machine
|
2
|
+
==============
|
3
|
+
|
4
|
+
A ruby implementation of the Policy Machine authorization formalism. You can find the NIST specification for Policy
|
5
|
+
Machines [here](http://csrc.nist.gov/pm/documents/pm_report-rev-x_final.pdf).
|
6
|
+
|
7
|
+
Note that prohibitions, obligations and multiple policy classes have not yet been implemented. These aspects of the Policy Machine
|
8
|
+
will be included in future versions of this gem.
|
9
|
+
|
10
|
+
# Installation
|
11
|
+
|
12
|
+
Add the following to your Gemfile:
|
13
|
+
```
|
14
|
+
gem 'policy_machine'
|
15
|
+
```
|
16
|
+
|
17
|
+
# Usage
|
18
|
+
```
|
19
|
+
require 'policy_machine'
|
20
|
+
require 'policy_machine_storage_adapters/in_memory'
|
21
|
+
|
22
|
+
policy_machine = PolicyMachine.new('my_policy_machine', ::PolicyMachineStorageAdapter::InMemory)
|
23
|
+
|
24
|
+
# This PM is taken from the policy machine spec at http://csrc.nist.gov/pm/documents/pm_report-rev-x_final.pdf,
|
25
|
+
# Figure 4. (pg. 19)
|
26
|
+
|
27
|
+
# Users
|
28
|
+
u1 = policy_machine.create_user('u1')
|
29
|
+
u2 = policy_machine.create_user('u2')
|
30
|
+
u3 = policy_machine.create_user('u3')
|
31
|
+
|
32
|
+
# Objects
|
33
|
+
o1 = policy_machine.create_object('o1')
|
34
|
+
o2 = policy_machine.create_object('o2')
|
35
|
+
o3 = policy_machine.create_object('o3')
|
36
|
+
|
37
|
+
# User Attributes
|
38
|
+
group1 = policy_machine.create_user_attribute('Group1')
|
39
|
+
group2 = policy_machine.create_user_attribute('Group2')
|
40
|
+
division = policy_machine.create_user_attribute('Division')
|
41
|
+
|
42
|
+
# Object Attributes
|
43
|
+
project1 = policy_machine.create_object_attribute('Project1')
|
44
|
+
project2 = policy_machine.create_object_attribute('Project2')
|
45
|
+
projects = policy_machine.create_object_attribute('Projects')
|
46
|
+
|
47
|
+
# Operations
|
48
|
+
r = policy_machine.create_operation('read')
|
49
|
+
w = policy_machine.create_operation('write')
|
50
|
+
|
51
|
+
# Assignments
|
52
|
+
policy_machine.add_assignment(u1, group1)
|
53
|
+
policy_machine.add_assignment(u2, group2)
|
54
|
+
policy_machine.add_assignment(u3, division)
|
55
|
+
policy_machine.add_assignment(group1, division)
|
56
|
+
policy_machine.add_assignment(group2, division)
|
57
|
+
policy_machine.add_assignment(o1, project1)
|
58
|
+
policy_machine.add_assignment(o2, project1)
|
59
|
+
policy_machine.add_assignment(o3, project2)
|
60
|
+
policy_machine.add_assignment(project1, projects)
|
61
|
+
policy_machine.add_assignment(project2, projects)
|
62
|
+
|
63
|
+
# Associations
|
64
|
+
policy_machine.add_association(group1, Set.new([w]), project1)
|
65
|
+
policy_machine.add_association(group2, Set.new([w]), project2)
|
66
|
+
policy_machine.add_association(division, Set.new([r]), projects)
|
67
|
+
|
68
|
+
# List all privileges encoded in the policy machine
|
69
|
+
policy_machine.privileges
|
70
|
+
|
71
|
+
# Returns true
|
72
|
+
policy_machine.is_privilege?(u1, w, o1)
|
73
|
+
|
74
|
+
# Returns false
|
75
|
+
policy_machine.is_privilege?(u3, w, o3)
|
76
|
+
```
|
77
|
+
|
78
|
+
# Storage Adapters
|
79
|
+
|
80
|
+
Note that the Policy Machine in the above example stores policy elements in memory. Other persistent
|
81
|
+
storage options are available in `lib/policy_machine_storage_adapters`.
|
82
|
+
|
83
|
+
*Neography*
|
84
|
+
|
85
|
+
The Neography storage adapter uses the neo4j graph database, which must be installed separately,
|
86
|
+
and `gem 'neography'`. This should not be used in production since the interface is slow.
|
87
|
+
|
88
|
+
*ActiveRecord*
|
89
|
+
|
90
|
+
The ActiveRecord storage adapter talks to your existing MySQL database via your preconfigured
|
91
|
+
ActiveRecord. You'll need to run `rails generate policy_machine migration` to add the necessary
|
92
|
+
tables to your database.
|
93
|
+
|
94
|
+
If you'd like to make your own storage adapter, see See [CONTRIBUTING.md](CONTRIBUTING.md).
|
95
|
+
|
96
|
+
# Contributing
|
97
|
+
|
98
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rails/generators/active_record/migration/migration_generator'
|
2
|
+
|
3
|
+
class PolicyMachineGenerator < ::ActiveRecord::Generators::MigrationGenerator
|
4
|
+
desc "Create a migration to store Policy Machine elements in your database"
|
5
|
+
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
args[0] = ['generate_policy_machine']
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class GeneratePolicyMachine < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
|
4
|
+
create_table :policy_elements do |t|
|
5
|
+
t.string :unique_identifier, null: false
|
6
|
+
t.string :policy_machine_uuid
|
7
|
+
t.string :type, null: false
|
8
|
+
t.text :extra_attributes
|
9
|
+
end
|
10
|
+
add_index :policy_elements, [:unique_identifier], unique: true
|
11
|
+
add_index :policy_elements, [:type]
|
12
|
+
|
13
|
+
create_table :policy_element_associations do |t|
|
14
|
+
t.integer :user_attribute_id, null: false
|
15
|
+
t.integer :object_attribute_id, null: false
|
16
|
+
end
|
17
|
+
add_index :policy_element_associations, [:user_attribute_id, :object_attribute_id], name: 'index_pe_assocs_on_ua_and_oa'
|
18
|
+
|
19
|
+
create_table :transitive_closure, id: false do |t|
|
20
|
+
t.integer :ancestor_id, null: false
|
21
|
+
t.integer :descendant_id, null: false
|
22
|
+
end
|
23
|
+
add_index :transitive_closure, [:ancestor_id, :descendant_id], unique: true
|
24
|
+
add_index :transitive_closure, [:descendant_id]
|
25
|
+
|
26
|
+
create_table :assignments do |t|
|
27
|
+
t.integer :parent_id, null: false
|
28
|
+
t.integer :child_id, null: false
|
29
|
+
end
|
30
|
+
add_index :assignments, [:parent_id, :child_id], unique: true
|
31
|
+
add_index :assignments, [:child_id]
|
32
|
+
|
33
|
+
create_table :operations_policy_element_associations, id: false do |t|
|
34
|
+
t.integer :policy_element_association_id, null: false
|
35
|
+
t.integer :operation_id, null: false
|
36
|
+
end
|
37
|
+
add_index :operations_policy_element_associations, [:policy_element_association_id, :operation_id], unique: true, name: 'index_pe_assoc_os_on_assoc_and_o'
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'policy_machine/policy_element'
|
2
|
+
require 'policy_machine/association'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
# require all adapters
|
8
|
+
Dir.glob(File.dirname(File.absolute_path(__FILE__)) + '/policy_machine_storage_adapters/*.rb').each{ |f| require f }
|
9
|
+
|
10
|
+
class PolicyMachine
|
11
|
+
POLICY_ELEMENT_TYPES = %w(user user_attribute object object_attribute operation policy_class)
|
12
|
+
|
13
|
+
attr_accessor :name
|
14
|
+
attr_reader :uuid
|
15
|
+
attr_reader :policy_machine_storage_adapter
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@name = (options[:name] || options['name'] || 'default_policy_machine').to_s.strip
|
19
|
+
@uuid = (options[:uuid] || options['uuid'] || SecureRandom.uuid).to_s.strip
|
20
|
+
policy_machine_storage_adapter_class = options[:storage_adapter] || options['storage_adapter'] || ::PolicyMachineStorageAdapter::InMemory
|
21
|
+
@policy_machine_storage_adapter = policy_machine_storage_adapter_class.new
|
22
|
+
|
23
|
+
raise(ArgumentError, "uuid cannot be blank") if @uuid.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Persist an assignment in this policy machine.
|
28
|
+
# An assignment is a binary relation between two existing policy elements.
|
29
|
+
# Some policy element types cannot be assigned to other types. See the NIST
|
30
|
+
# spec for details.
|
31
|
+
#
|
32
|
+
def add_assignment(src_policy_element, dst_policy_element)
|
33
|
+
assert_policy_element_in_machine(src_policy_element)
|
34
|
+
assert_policy_element_in_machine(dst_policy_element)
|
35
|
+
|
36
|
+
src_policy_element.assign_to(dst_policy_element)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Remove an assignment in this policy machine.
|
41
|
+
#
|
42
|
+
def remove_assignment(src_policy_element, dst_policy_element)
|
43
|
+
assert_policy_element_in_machine(src_policy_element)
|
44
|
+
assert_policy_element_in_machine(dst_policy_element)
|
45
|
+
|
46
|
+
src_policy_element.unassign(dst_policy_element)
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Add an association between a user_attribute, an operation_set and an object_attribute
|
51
|
+
# in this policy machine.
|
52
|
+
#
|
53
|
+
def add_association(user_attribute_pe, operation_set, object_attribute_pe)
|
54
|
+
assert_policy_element_in_machine(user_attribute_pe)
|
55
|
+
operation_set.each{ |op| assert_policy_element_in_machine(op) }
|
56
|
+
assert_policy_element_in_machine(object_attribute_pe)
|
57
|
+
|
58
|
+
PM::Association.create(user_attribute_pe, operation_set, object_attribute_pe, @uuid, @policy_machine_storage_adapter)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Can we derive a privilege of the form (u, op, o) from this policy machine?
|
63
|
+
# user_or_attribute is a user or user_attribute.
|
64
|
+
# operation is an operation.
|
65
|
+
# object_or_attribute is an object or object attribute.
|
66
|
+
#
|
67
|
+
# TODO: add option to ignore policy classes to allow consumer to speed up this method.
|
68
|
+
def is_privilege?(user_or_attribute, operation, object_or_attribute, options = {})
|
69
|
+
unless user_or_attribute.is_a?(PM::User) || user_or_attribute.is_a?(PM::UserAttribute)
|
70
|
+
raise(ArgumentError, "user_attribute_pe must be a User or UserAttribute.")
|
71
|
+
end
|
72
|
+
|
73
|
+
unless operation.is_a?(PM::Operation)
|
74
|
+
raise(ArgumentError, "operation must be an Operation.")
|
75
|
+
end
|
76
|
+
|
77
|
+
unless object_or_attribute.is_a?(PM::Object) || object_or_attribute.is_a?(PM::ObjectAttribute)
|
78
|
+
raise(ArgumentError, "object_or_attribute must either be an Object or ObjectAttribute.")
|
79
|
+
end
|
80
|
+
|
81
|
+
# Try to get associations to check from options
|
82
|
+
associations = options[:associations] || options['associations']
|
83
|
+
if associations
|
84
|
+
raise(ArgumentError, "expected options[:associations] to be an Array; got #{associations.class}") unless associations.is_a?(Array)
|
85
|
+
raise(ArgumentError, "options[:associations] cannot be empty") if associations.empty?
|
86
|
+
raise(ArgumentError, "expected each element of options[:associations] to be a PM::Association") unless associations.all?{|a| a.is_a?(PM::Association)}
|
87
|
+
|
88
|
+
associations.keep_if{ |assoc| assoc.includes_operation?(operation) }
|
89
|
+
return false if associations.empty?
|
90
|
+
else
|
91
|
+
associations = operation.associations
|
92
|
+
end
|
93
|
+
|
94
|
+
# Is a privilege iff options[:in_user_attribute] is involved (given options[:in_user_attribute] is not nil)
|
95
|
+
in_user_attribute = options[:in_user_attribute] || options['in_user_attribute']
|
96
|
+
if in_user_attribute
|
97
|
+
unless in_user_attribute.is_a?(PM::UserAttribute)
|
98
|
+
raise(ArgumentError, "expected options[:in_user_attribute] to be a PM::UserAttribute; got #{in_user_attribute.class}")
|
99
|
+
end
|
100
|
+
if user_or_attribute.connected?(in_user_attribute)
|
101
|
+
user_or_attribute = in_user_attribute
|
102
|
+
else
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Is a privilege iff options[:in_object_attribute] is involved (given options[:in_object_attribute] is not nil)
|
108
|
+
in_object_attribute = options[:in_object_attribute] || options['in_object_attribute']
|
109
|
+
if in_object_attribute
|
110
|
+
unless in_object_attribute.is_a?(PM::ObjectAttribute)
|
111
|
+
raise(ArgumentError, "expected options[:in_object_attribute] to be a PM::ObjectAttribute; got #{in_object_attribute.class}")
|
112
|
+
end
|
113
|
+
if object_or_attribute.connected?(in_object_attribute)
|
114
|
+
object_or_attribute = in_object_attribute
|
115
|
+
else
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
policy_classes_containing_object = object_or_attribute.policy_classes
|
121
|
+
if policy_classes_containing_object.empty?
|
122
|
+
is_privilege_single_policy_class(user_or_attribute, object_or_attribute, associations)
|
123
|
+
else
|
124
|
+
is_privilege_multiple_policy_classes(user_or_attribute, object_or_attribute, associations, policy_classes_containing_object)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Returns an array of all privileges encoded in this
|
130
|
+
# policy machine. Each privilege is of the form:
|
131
|
+
# [PM::User, PM::Operation, PM::Object]
|
132
|
+
#
|
133
|
+
# TODO: might make privilege a class of its own
|
134
|
+
def privileges
|
135
|
+
privileges = []
|
136
|
+
|
137
|
+
users.each do |user|
|
138
|
+
operations.each do |operation|
|
139
|
+
objects.each do |object|
|
140
|
+
if is_privilege?(user, operation, object)
|
141
|
+
privileges << [user, operation, object]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
privileges
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Returns an array of all user_attributes a PM::User is assigned to,
|
152
|
+
# directly or indirectly.
|
153
|
+
def list_user_attributes(user)
|
154
|
+
unless user.is_a?(PM::User)
|
155
|
+
raise(ArgumentError, "Expected a PM::User, got a #{user.class}")
|
156
|
+
end
|
157
|
+
assert_policy_element_in_machine(user)
|
158
|
+
user.user_attributes(@policy_machine_storage_adapter)
|
159
|
+
end
|
160
|
+
|
161
|
+
POLICY_ELEMENT_TYPES.each do |pe_type|
|
162
|
+
pm_class = "PM::#{pe_type.camelize}".constantize
|
163
|
+
|
164
|
+
##
|
165
|
+
# Define a create method for each policy element type, as in create_user
|
166
|
+
# Each method takes one argument, the unique_identifier of the policy element.
|
167
|
+
#
|
168
|
+
define_method("create_#{pe_type}") do |unique_identifier, extra_attributes = {}|
|
169
|
+
# when creating a policy element, we provide a unique_identifier, the uuid of this policy machine
|
170
|
+
# and a policy machine storage adapter to allow us to persist the policy element.
|
171
|
+
pm_class.send(:create, unique_identifier, @uuid, @policy_machine_storage_adapter, extra_attributes)
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Define an "all" method for each policy element type, as in .users or .object_attributes
|
176
|
+
# This will return all persisted of the elements of this type. If an options hash is passed
|
177
|
+
# then only elements that match all specified attributes will be returned.
|
178
|
+
#
|
179
|
+
define_method(pe_type.pluralize) do |options = {}|
|
180
|
+
# TODO: We might want to scope by the uuid of this policy machine in the request to the persistent store, rather than
|
181
|
+
# here, after records have already been retrieved.
|
182
|
+
# TODO: When the policy machine raises a NoMethoError, we should log a nice message
|
183
|
+
# saying that the underlying policy element class doesn't implement 'all'. Do
|
184
|
+
# it when we have a logger, though.
|
185
|
+
all_found = pm_class.send(:all, @policy_machine_storage_adapter, options)
|
186
|
+
all_found.select{ |pe| pe.policy_machine_uuid == uuid }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Execute the passed-in block transactionally: any error raised out of the block causes
|
192
|
+
# all the block's changes to be rolled back.
|
193
|
+
# TODO: Possibly rescue NotImplementError and warn.
|
194
|
+
def transaction(&block)
|
195
|
+
policy_machine_storage_adapter.transaction(&block)
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
# Raise unless the argument is a policy element.
|
201
|
+
def assert_policy_element_in_machine(arg_pe)
|
202
|
+
unless arg_pe.is_a?(PM::PolicyElement)
|
203
|
+
raise(ArgumentError, "arg must each be a kind of PolicyElement; got #{arg_pe.class.name} instead")
|
204
|
+
end
|
205
|
+
unless arg_pe.policy_machine_uuid == self.uuid
|
206
|
+
raise(ArgumentError, "#{arg_pe.unique_identifier} is not in policy machine with uuid #{self.uuid}")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# According to the NIST spec: "the triple (u, op, o) is a privilege, iff there
|
211
|
+
# exists an association (ua, ops, oa), such that user u→+ua, op ∈ ops, and o→*oa."
|
212
|
+
# Note: this method assumes that the caller has already checked that the given operation is in the operation_set
|
213
|
+
# for all associations provided.
|
214
|
+
def is_privilege_single_policy_class(user_or_attribute, object_or_attribute, associations)
|
215
|
+
# does there exist an association (ua, ops, oa), such that user u→+ua, op ∈ ops, and o→*oa?
|
216
|
+
associations.any? do |assoc|
|
217
|
+
user_or_attribute.connected?(assoc.user_attribute) && object_or_attribute.connected?(assoc.object_attribute)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# According to the NIST spec: "In multiple policy class situations, the triple (u, op, o) is a PM privilege, iff for
|
222
|
+
# each policy class pcl that contains o, there exists an association (uai, opsj, oak),
|
223
|
+
# such that user u→+uai, op ∈ opsj, o→*oak, and oak→+pcl."
|
224
|
+
# Note: this method assumes that the caller has already checked that the given operation is in the operation_set
|
225
|
+
# for all associations provided.
|
226
|
+
def is_privilege_multiple_policy_classes(user_or_attribute, object_or_attribute, associations, policy_classes_containing_object)
|
227
|
+
policy_classes_containing_object.all? do |pc|
|
228
|
+
associations.any? do |assoc|
|
229
|
+
user_or_attribute.connected?(assoc.user_attribute) &&
|
230
|
+
object_or_attribute.connected?(assoc.object_attribute) &&
|
231
|
+
assoc.object_attribute.connected?(pc)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|