conjur-asset-dsl2 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +2 -0
- data/.gitignore +14 -0
- data/.project +18 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/CHANGELOG +1 -0
- data/Dockerfile.dev +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +248 -0
- data/Rakefile +18 -0
- data/backup.tar +0 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/conjur-asset-dsl2.gemspec +32 -0
- data/jenkins.sh +36 -0
- data/lib/conjur/command/dsl2.rb +175 -0
- data/lib/conjur/dsl2/executor/base.rb +50 -0
- data/lib/conjur/dsl2/executor/create.rb +117 -0
- data/lib/conjur/dsl2/executor/deny.rb +13 -0
- data/lib/conjur/dsl2/executor/give.rb +12 -0
- data/lib/conjur/dsl2/executor/grant.rb +13 -0
- data/lib/conjur/dsl2/executor/permit.rb +16 -0
- data/lib/conjur/dsl2/executor/retire.rb +7 -0
- data/lib/conjur/dsl2/executor/revoke.rb +11 -0
- data/lib/conjur/dsl2/executor/update.rb +31 -0
- data/lib/conjur/dsl2/executor.rb +99 -0
- data/lib/conjur/dsl2/invalid.rb +12 -0
- data/lib/conjur/dsl2/plan.rb +49 -0
- data/lib/conjur/dsl2/planner/base.rb +215 -0
- data/lib/conjur/dsl2/planner/grants.rb +85 -0
- data/lib/conjur/dsl2/planner/permissions.rb +80 -0
- data/lib/conjur/dsl2/planner/record.rb +102 -0
- data/lib/conjur/dsl2/planner.rb +38 -0
- data/lib/conjur/dsl2/ruby/loader.rb +263 -0
- data/lib/conjur/dsl2/types/base.rb +376 -0
- data/lib/conjur/dsl2/types/create.rb +15 -0
- data/lib/conjur/dsl2/types/deny.rb +17 -0
- data/lib/conjur/dsl2/types/give.rb +14 -0
- data/lib/conjur/dsl2/types/grant.rb +24 -0
- data/lib/conjur/dsl2/types/member.rb +14 -0
- data/lib/conjur/dsl2/types/permit.rb +22 -0
- data/lib/conjur/dsl2/types/policy.rb +129 -0
- data/lib/conjur/dsl2/types/records.rb +243 -0
- data/lib/conjur/dsl2/types/retire.rb +14 -0
- data/lib/conjur/dsl2/types/revoke.rb +14 -0
- data/lib/conjur/dsl2/types/update.rb +16 -0
- data/lib/conjur/dsl2/yaml/handler.rb +400 -0
- data/lib/conjur/dsl2/yaml/loader.rb +29 -0
- data/lib/conjur-asset-dsl2-version.rb +7 -0
- data/lib/conjur-asset-dsl2.rb +27 -0
- data/syntax.md +147 -0
- metadata +237 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'conjur/dsl2/planner/base'
|
2
|
+
|
3
|
+
module Conjur
|
4
|
+
module DSL2
|
5
|
+
module Planner
|
6
|
+
class Grant < Base
|
7
|
+
# Plans a role grant.
|
8
|
+
#
|
9
|
+
# The Grant record can list multiple roles and members. Each member should
|
10
|
+
# be granted every role. If the +replace+ option is set, then any existing
|
11
|
+
# grant on a role that is *not* given should be revoked, except for role admins.
|
12
|
+
def do_plan
|
13
|
+
|
14
|
+
roles = Array(record.roles)
|
15
|
+
members = Array(record.members)
|
16
|
+
given_grants = Hash.new { |hash, key| hash[key] = [] }
|
17
|
+
given_admins = Set.new
|
18
|
+
requested_grants = Hash.new { |hash, key| hash[key] = [] }
|
19
|
+
|
20
|
+
# Check all roles / members involved
|
21
|
+
(roles + members.map(&:role)).each do |role|
|
22
|
+
error("role not found: #{scoped_roleid(role)} in #{plan.roles_created.to_a}") unless role_exists?(role)
|
23
|
+
end
|
24
|
+
|
25
|
+
roles.each do |role|
|
26
|
+
grants = begin
|
27
|
+
api.role(scoped_roleid(role)).members
|
28
|
+
rescue RestClient::ResourceNotFound
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
32
|
+
grants.each do |grant|
|
33
|
+
member_roleid = grant.member.roleid
|
34
|
+
given_grants[scoped_roleid(role)].push [ member_roleid, grant.admin_option ]
|
35
|
+
given_admins << member_roleid if grant.admin_option
|
36
|
+
end
|
37
|
+
members.each do |member|
|
38
|
+
requested_grants[scoped_roleid(role)].push [ scoped_roleid(member.role), !!member.admin ]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
roles.each do |role|
|
43
|
+
roleid = scoped_roleid(role)
|
44
|
+
given = given_grants[roleid]
|
45
|
+
requested = requested_grants[roleid]
|
46
|
+
|
47
|
+
(Set.new(requested) - Set.new(given)).each do |p|
|
48
|
+
member, admin = p
|
49
|
+
grant = Conjur::DSL2::Types::Grant.new
|
50
|
+
grant.role = role_record roleid
|
51
|
+
grant.member = Conjur::DSL2::Types::Member.new role_record(member)
|
52
|
+
grant.member.admin = true if admin
|
53
|
+
action grant
|
54
|
+
end
|
55
|
+
|
56
|
+
if record.replace
|
57
|
+
(Set.new(given) - Set.new(requested)).each do |p|
|
58
|
+
member, _ = p
|
59
|
+
member_roleid = role_record(member).roleid(account)
|
60
|
+
next if given_admins.member?(member_roleid)
|
61
|
+
revoke = Conjur::DSL2::Types::Revoke.new
|
62
|
+
revoke.role = role_record roleid
|
63
|
+
revoke.member = role_record(member)
|
64
|
+
action revoke
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Revoke < Base
|
72
|
+
def do_plan
|
73
|
+
Array(record.roles).each do |role|
|
74
|
+
Array(record.members).each do |member|
|
75
|
+
revoke = Conjur::DSL2::Types::Revoke.new
|
76
|
+
revoke.role = role
|
77
|
+
revoke.member = member
|
78
|
+
action revoke
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'conjur/dsl2/planner/base'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Conjur
|
5
|
+
module DSL2
|
6
|
+
module Planner
|
7
|
+
# Plans a permission.
|
8
|
+
#
|
9
|
+
# The Permit record can list multiple roles, privileges, and resources. Each privilege should
|
10
|
+
# be allowed to each role on each resource. If the +replace+ option is set, then any existing
|
11
|
+
# privilege on an existing resource that is *not* given should be denied.
|
12
|
+
class Permit < Base
|
13
|
+
def do_plan
|
14
|
+
|
15
|
+
resources = Array(record.resources)
|
16
|
+
privileges = Array(record.privilege)
|
17
|
+
given_permissions = Hash.new { |hash, key| hash[key] = [] }
|
18
|
+
requested_permissions = Hash.new { |hash, key| hash[key] = [] }
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
resources.each do |resource|
|
23
|
+
permissions = begin
|
24
|
+
JSON.parse(api.resource(scoped_resourceid(resource)).get)['permissions']
|
25
|
+
rescue RestClient::ResourceNotFound
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
permissions.each do |permission|
|
30
|
+
if privileges.member?(permission['privilege'])
|
31
|
+
given_permissions[[permission['privilege'], permission['resource']]].push [ permission['role'], permission['grant_option'] ]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
privileges.each do |privilege|
|
36
|
+
Array(record.roles).each do |role|
|
37
|
+
requested_permissions[[privilege, scoped_resourceid(resource)]].push [ scoped_roleid(role.role), !!role.admin ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
resources.each do |resource|
|
43
|
+
error("resource not found: #{resource}") unless resource_exists?(resource)
|
44
|
+
|
45
|
+
privileges.each do |privilege|
|
46
|
+
|
47
|
+
target = scoped_resourceid(resource)
|
48
|
+
given = given_permissions[[privilege, target]]
|
49
|
+
requested = requested_permissions[[privilege, target]]
|
50
|
+
|
51
|
+
(Set.new(requested) - Set.new(given)).each do |p|
|
52
|
+
role, admin = p
|
53
|
+
|
54
|
+
error("role not found: #{role}") unless role_exists?(role)
|
55
|
+
|
56
|
+
permit = Conjur::DSL2::Types::Permit.new
|
57
|
+
permit.resource = resource_record target
|
58
|
+
permit.privilege = privilege
|
59
|
+
permit.role = Conjur::DSL2::Types::Member.new role_record(role)
|
60
|
+
permit.role.admin = true if admin
|
61
|
+
action permit
|
62
|
+
end
|
63
|
+
|
64
|
+
if record.replace
|
65
|
+
(Set.new(given) - Set.new(requested)).each do |p|
|
66
|
+
role, admin = p
|
67
|
+
deny = Conjur::DSL2::Types::Deny.new
|
68
|
+
deny.resource = resource_record target
|
69
|
+
deny.privilege = privilege
|
70
|
+
deny.role = role_record(role)
|
71
|
+
action deny
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'conjur/dsl2/planner/base'
|
2
|
+
|
3
|
+
module Conjur
|
4
|
+
module DSL2
|
5
|
+
module Planner
|
6
|
+
module ActsAsRecord
|
7
|
+
# Record objects sort before everything else
|
8
|
+
def <=> other
|
9
|
+
other.kind_of?(ActsAsRecord) ? 0 : -1
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_plan
|
13
|
+
if object.exists?
|
14
|
+
update_record
|
15
|
+
else
|
16
|
+
create_record
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"<#{self.class.name} #{record.to_s}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Role < Base
|
26
|
+
include ActsAsRecord
|
27
|
+
|
28
|
+
alias object role
|
29
|
+
end
|
30
|
+
|
31
|
+
class Resource < Base
|
32
|
+
include ActsAsRecord
|
33
|
+
|
34
|
+
alias object resource
|
35
|
+
end
|
36
|
+
|
37
|
+
class Record < Base
|
38
|
+
include ActsAsRecord
|
39
|
+
|
40
|
+
def object
|
41
|
+
@object ||= api.send(record.resource_kind, scoped_id(record))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Webservice < Resource
|
46
|
+
end
|
47
|
+
|
48
|
+
class Policy < Base
|
49
|
+
def do_plan
|
50
|
+
role = record.role(default_account)
|
51
|
+
Role.new(role, api).tap do |role|
|
52
|
+
role.plan = plan
|
53
|
+
role.do_plan
|
54
|
+
end
|
55
|
+
|
56
|
+
if record.body.nil?
|
57
|
+
error('missing body element in policy')
|
58
|
+
end
|
59
|
+
|
60
|
+
plan.ownerid = role.roleid(account)
|
61
|
+
resource = record.resource(default_account)
|
62
|
+
if record.annotations
|
63
|
+
resource.annotations = record.annotations
|
64
|
+
end
|
65
|
+
|
66
|
+
Resource.new(resource, api).tap do |resource|
|
67
|
+
resource.plan = plan
|
68
|
+
resource.do_plan
|
69
|
+
end
|
70
|
+
|
71
|
+
planners = record.body.map do |record|
|
72
|
+
Planner.planner_for(record, api)
|
73
|
+
end.sort
|
74
|
+
|
75
|
+
planners.each do |planner|
|
76
|
+
ownerid = plan.ownerid
|
77
|
+
begin
|
78
|
+
plan.policy = self.record
|
79
|
+
|
80
|
+
# Set the ownerid to the namespace-scoped roleid of the policy
|
81
|
+
ownerid = plan.policy.roleid(account)
|
82
|
+
if plan.namespace
|
83
|
+
account, kind, id = ownerid.split(':', 3)
|
84
|
+
ownerid = [ account, kind, [ plan.namespace, id ].join("/") ].join(":")
|
85
|
+
end
|
86
|
+
ownerid = ownerid
|
87
|
+
plan.ownerid = ownerid
|
88
|
+
|
89
|
+
planner.plan = plan
|
90
|
+
planner.trace("planning...")
|
91
|
+
planner.do_plan
|
92
|
+
planner.trace("ok!")
|
93
|
+
ensure
|
94
|
+
plan.policy = nil
|
95
|
+
plan.ownerid = ownerid
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'conjur/dsl2/plan'
|
2
|
+
require 'conjur/dsl2/planner/record'
|
3
|
+
require 'conjur/dsl2/planner/permissions'
|
4
|
+
require 'conjur/dsl2/planner/grants'
|
5
|
+
|
6
|
+
module Conjur
|
7
|
+
module DSL2
|
8
|
+
module Planner
|
9
|
+
class << self
|
10
|
+
def plan records, api, options = {}
|
11
|
+
namespace = options[:namespace]
|
12
|
+
ownerid = options[:ownerid]
|
13
|
+
Plan.new.tap do |plan|
|
14
|
+
plan.namespace = namespace if namespace
|
15
|
+
plan.ownerid = ownerid if ownerid
|
16
|
+
records.map{ |record| planner_for(record, api) }.sort.each do |planner|
|
17
|
+
planner.plan = plan
|
18
|
+
begin
|
19
|
+
planner.do_plan
|
20
|
+
ensure
|
21
|
+
planner.plan = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def planner_for record, api
|
28
|
+
cls = begin
|
29
|
+
const_get record.class.name.split("::")[-1]
|
30
|
+
rescue NameError
|
31
|
+
Record
|
32
|
+
end
|
33
|
+
cls.new record, api
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
class Object
|
2
|
+
# Dear Ruby, I wish instance variables order was stable, because if it was
|
3
|
+
# then YAML would always come out the same.
|
4
|
+
def to_yaml_properties
|
5
|
+
instance_variables.sort
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Conjur
|
10
|
+
module DSL2
|
11
|
+
module Ruby
|
12
|
+
module RecordLoader
|
13
|
+
def respond_to_missing? sym, include_all = false
|
14
|
+
super or Conjur::DSL2::Types.const_get sym.to_s.classify rescue nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Implement +method_missing+ to reference basic types like Group, User, Layer, etc.
|
19
|
+
# Anything from Conjur::DSL2::Types is fair game.
|
20
|
+
module RecordReferenceFactory
|
21
|
+
include RecordLoader
|
22
|
+
|
23
|
+
# The record can have a constructor with 0 or 1 arguments. If it takes 1 argument,
|
24
|
+
# it will be populated with the first +args+, if any. It's assumed to be the id.
|
25
|
+
def method_missing sym, *args, &block
|
26
|
+
kind = Conjur::DSL2::Types.const_get sym.to_s.classify rescue nil
|
27
|
+
if kind
|
28
|
+
object = kind.new(*args)
|
29
|
+
raise "#{kind.short_name} is not createable here" unless object.role? || object.resource?
|
30
|
+
handle_object object, &block
|
31
|
+
object
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_object object, &block
|
38
|
+
# pass
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Construct record properties in a block and yield the record to the loader.
|
43
|
+
module RecordFactory
|
44
|
+
include RecordReferenceFactory
|
45
|
+
|
46
|
+
def handle_object object, &block
|
47
|
+
push object
|
48
|
+
do_scope object, &block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class YAMLList < Array
|
53
|
+
def tag
|
54
|
+
[ "!", self.class.name.split("::")[-1].underscore ].join
|
55
|
+
end
|
56
|
+
|
57
|
+
def encode_with coder
|
58
|
+
coder.represent_seq tag, self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Tagless
|
63
|
+
def tag; nil; end
|
64
|
+
end
|
65
|
+
|
66
|
+
module CustomStatement
|
67
|
+
def custom_statement handler, &block
|
68
|
+
record = yield
|
69
|
+
class << record
|
70
|
+
include RecordReferenceFactory
|
71
|
+
end
|
72
|
+
push record
|
73
|
+
do_scope record, &handler
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module Grants
|
78
|
+
include CustomStatement
|
79
|
+
|
80
|
+
def grant &block
|
81
|
+
custom_statement(block) do
|
82
|
+
Conjur::DSL2::Types::Grant.new
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def revoke &block
|
87
|
+
custom_statement(block) do
|
88
|
+
Conjur::DSL2::Types::Revoke.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module Permissions
|
94
|
+
include CustomStatement
|
95
|
+
|
96
|
+
def permit privilege, &block
|
97
|
+
custom_statement(block) do
|
98
|
+
Conjur::DSL2::Types::Permit.new(privilege)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def give &block
|
103
|
+
custom_statement(block) do
|
104
|
+
Conjur::DSL2::Types::Give.new
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def retire &block
|
109
|
+
custom_statement(block) do
|
110
|
+
Conjur::DSL2::Types::Retire.new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Entitlements will allow creation of any record, as well as declaration
|
116
|
+
# of permit, deny, grant and revoke.
|
117
|
+
class Entitlements < YAMLList
|
118
|
+
include Tagless
|
119
|
+
include RecordFactory
|
120
|
+
include Grants
|
121
|
+
include Permissions
|
122
|
+
|
123
|
+
def policy id=nil, &block
|
124
|
+
policy = Policy.new
|
125
|
+
policy.id(id) unless id.nil?
|
126
|
+
push policy
|
127
|
+
|
128
|
+
do_scope policy, &block
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Body < YAMLList
|
133
|
+
include RecordFactory
|
134
|
+
include Grants
|
135
|
+
include Permissions
|
136
|
+
end
|
137
|
+
|
138
|
+
# Policy includes the functionality of Entitlements, wrapped in a
|
139
|
+
# policy role, policy resource, policy id and policy version.
|
140
|
+
class Policy < Conjur::DSL2::Types::Base
|
141
|
+
include Conjur::DSL2::Types::ActsAsResource
|
142
|
+
include Conjur::DSL2::Types::ActsAsRole
|
143
|
+
|
144
|
+
def body &block
|
145
|
+
singleton :body, lambda { Body.new }, &block
|
146
|
+
@body
|
147
|
+
end
|
148
|
+
|
149
|
+
def body= body
|
150
|
+
@body = body
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
|
155
|
+
def singleton id, factory, &block
|
156
|
+
object = instance_variable_get("@#{id}")
|
157
|
+
unless object
|
158
|
+
object = factory.call
|
159
|
+
class << object
|
160
|
+
include Tagless
|
161
|
+
end
|
162
|
+
instance_variable_set("@#{id}", object)
|
163
|
+
end
|
164
|
+
do_scope object, &block
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module Delegation
|
169
|
+
def respond_to? sym, include_all = false
|
170
|
+
super or scope.respond_to?(sym, include_all)
|
171
|
+
end
|
172
|
+
|
173
|
+
def method_missing(sym, *args, &block)
|
174
|
+
if scope.respond_to?(sym)
|
175
|
+
scope.send sym, *args, &block
|
176
|
+
else
|
177
|
+
raise NoMethodError, "undefined method `#{sym}` for #{scope}:#{scope.class}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class Loader
|
183
|
+
include Delegation
|
184
|
+
|
185
|
+
attr_reader :script, :filename
|
186
|
+
|
187
|
+
class << self
|
188
|
+
def load_file filename
|
189
|
+
load File.read(filename), filename
|
190
|
+
end
|
191
|
+
|
192
|
+
def load yaml, filename = nil
|
193
|
+
create(yaml, filename).load
|
194
|
+
end
|
195
|
+
|
196
|
+
def create(yaml, filename=nil)
|
197
|
+
new(yaml, filename)
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
def initialize script, filename = nil
|
203
|
+
@script = script
|
204
|
+
@filename = filename
|
205
|
+
@scope = []
|
206
|
+
end
|
207
|
+
|
208
|
+
def loader
|
209
|
+
self
|
210
|
+
end
|
211
|
+
|
212
|
+
def load root = nil
|
213
|
+
args = [ script ]
|
214
|
+
args << filename if filename
|
215
|
+
root ||= Entitlements.new
|
216
|
+
do_scope root do
|
217
|
+
instance_eval(*args)
|
218
|
+
end
|
219
|
+
root
|
220
|
+
end
|
221
|
+
|
222
|
+
def push_scope obj
|
223
|
+
@scope.push obj
|
224
|
+
end
|
225
|
+
|
226
|
+
def scope
|
227
|
+
@scope.last
|
228
|
+
end
|
229
|
+
|
230
|
+
def pop_scope
|
231
|
+
@scope.pop
|
232
|
+
end
|
233
|
+
|
234
|
+
def do_scope obj, &block
|
235
|
+
push_scope obj
|
236
|
+
class << obj
|
237
|
+
attr_accessor :loader
|
238
|
+
|
239
|
+
def do_scope obj, &block
|
240
|
+
loader.do_scope obj, &block
|
241
|
+
end
|
242
|
+
|
243
|
+
def scope
|
244
|
+
loader.scope
|
245
|
+
end
|
246
|
+
|
247
|
+
def to_yaml_properties
|
248
|
+
super - [ :"@loader" ]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
obj.loader = self
|
252
|
+
begin
|
253
|
+
yield if block_given?
|
254
|
+
ensure
|
255
|
+
pop_scope
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
Psych.add_tag "!policy", Policy
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|