popel-active_acl_plus 0.4.4

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.
@@ -0,0 +1,29 @@
1
+ module ActiveAcl
2
+ CONTROLLERS={}
3
+ GROUP_CLASSES={}
4
+ ACCESS_CLASSES={}
5
+
6
+ def self.register_group(klass,handler)
7
+ GROUP_CLASSES[klass.base_class.name]=handler
8
+ end
9
+ def self.register_object(klass,handler)
10
+ ACCESS_CLASSES[klass.base_class.name]=handler
11
+ end
12
+ def self.group_handler(klass)
13
+ GROUP_CLASSES[klass.base_class.name]
14
+ end
15
+ def self.object_handler(klass)
16
+ ACCESS_CLASSES[klass.base_class.name]
17
+ end
18
+ def self.is_access_group?(klass)
19
+ !!GROUP_CLASSES[klass.base_class.name]
20
+ end
21
+ def self.is_access_object?(klass)
22
+ !!ACCESS_CLASSES[klass.base_class.name]
23
+ end
24
+ def self.from_classes
25
+ ACCESS_CLASSES.keys.collect do |x|
26
+ x.split('::').join('/').underscore.pluralize.to_sym
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ # This is a cache adapter for the second level cache
2
+ # using the memcache daemon (http://www.danga.com/memcached).
3
+ # Sets itself as the cache adapter if the source file is loaded so a simple
4
+ # require is enough to activate the memcache. Before using the memcache, make sure to set MemcacheAdapter.cache.
5
+ #
6
+ # In environment.rb:
7
+ # require 'active_acl/cache/memcache_adapter'
8
+ # ActiveAcl::Cache::MemcacheAdapter.cache = MemCache.new('localhost:11211', :namespace => 'my_namespace')
9
+ # you can also set the time to leave:
10
+ # ActiveAcl::OPTIONS[:cache_privilege_timeout]= time_in_seconds
11
+ #
12
+ # Detailed instructions on how to set up the server can be found at http://dev.robotcoop.com/Libraries/memcache-client.
13
+ class ActiveAcl::Cache::MemcacheAdapter
14
+
15
+ # returns the memcache server
16
+ def self.cache #:nodoc:
17
+ @@cache
18
+ end
19
+
20
+ # sets the memcache server
21
+ def self.cache=(cache) #:nodoc:
22
+ @@cache = cache
23
+ end
24
+
25
+ # get a value from the cache
26
+ def self.get(key) #:nodoc:
27
+ value = @@cache.get(key)
28
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::' + (value.nil? ? 'MISS ' : 'HIT ')+ key.to_s
29
+ value
30
+ end
31
+
32
+ # set a value to the cache, specifying the time to live (ttl).
33
+ # Set ttl to 0 for unlimited.
34
+ def self.set(key, value, ttl) #:nodoc:
35
+ @@cache.set(key, value, ttl)
36
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::SET ' + key.to_s + ' TO ' + value.inspect.to_s + ' TTL ' + ttl.to_s
37
+ end
38
+
39
+ # purge data from cache.
40
+ def self.delete(key) #:nodoc:
41
+ @@cache.delete(key)
42
+ Rails.logger.debug 'GACL::SECOND_LEVEL_CACHE::DELETE ' + key.to_s
43
+ end
44
+ end
45
+
46
+ ActiveAcl::OPTIONS[:cache] = ActiveAcl::Cache::MemcacheAdapter
@@ -0,0 +1,22 @@
1
+ # This module contains different second level cache implementations. The second
2
+ # level cache caches the instance cache of an access object between requests.
3
+ # Cache adapter can be set with ActiveAcl::OPTIONS[:cache].
4
+ module ActiveAcl::Cache #:nodoc:
5
+
6
+ # The default second level cache dummy implementation, not implementing any
7
+ # caching functionality at all.
8
+ class NoCacheAdapter #:nodoc:
9
+ def self.get(key)
10
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::MISS ' + key.to_s
11
+ nil
12
+ end
13
+
14
+ def self.set(key, value, ttl)
15
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::SET ' + key.to_s + ' TO ' + value.inspect + ' TTL ' + ttl.to_s
16
+ end
17
+
18
+ def self.delete(key)
19
+ Rails.logger.debug 'ACTIVE_ACL::SECOND_LEVEL_CACHE::DISABLED::DELETE ' + key.to_s
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # Includes different DBMS adapters, some of them using C extensions to speed up DB access.
2
+ # DB adapter can be set with ActiveAcl::OPTIONS[:db].
3
+ module ActiveAcl::DB #:nodoc:
4
+
5
+ # Uses ActiveRecord for privilege queries. Should be compatible to all
6
+ # db types.
7
+ class ActiveRecordAdapter #:nodoc:
8
+ # Execute sql query against the DB, returning an array of results.
9
+ def self.query(sql)
10
+ ActiveRecord::Base.connection.select_all(sql)
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveAcl::OPTIONS[:db] = ActiveAcl::DB::ActiveRecordAdapter
@@ -0,0 +1,29 @@
1
+ require 'mysql'
2
+
3
+ # allow access to the real Mysql connection
4
+ class ActiveRecord::ConnectionAdapters::MysqlAdapter #:nodoc:
5
+ attr_reader :connection #:nodoc:
6
+ end
7
+
8
+ # Uses the native MySQL connection to do privilege selects. Should be around 20 % faster than
9
+ # ActiveRecord adapter. Sets itself as the DB adapter if the source file is loaded, so requiring it
10
+ # is enough to get it activated.
11
+ class ActiveAcl::DB::MySQLAdapter #:nodoc:
12
+
13
+ # Execute sql query against the DB, returning an array of results.
14
+ def self.query(sql)
15
+ Rails.logger.debug 'GACL::DB::EXECUTING QUERY ' + sql if Rails.logger.debug?
16
+ connection = ActiveRecord::Base.connection.connection
17
+ connection.query_with_result = true
18
+
19
+ result = connection.query(sql)
20
+ rows = []
21
+ result.each_hash do |hash|
22
+ rows << hash
23
+ end if result
24
+ result.free
25
+ rows
26
+ end
27
+ end
28
+
29
+ ActiveAcl::OPTIONS[:db] = ActiveAcl::DB::MySQLAdapter
@@ -0,0 +1,53 @@
1
+ module ActiveAcl
2
+ module Acts
3
+ module Grant
4
+ # grant_privilege!(Blog::DELETE,
5
+ # :on => blog,
6
+ # :section => 'blogging' or a Hash or an ActiveAcl::AclSection
7
+ # :acl => 'blogging_of_admins' or a hash or an ActiveAvl::Acl
8
+ # :target_as_object => true/false target is treated as access_object
9
+ def grant_privilege!(privilege,options={})
10
+ section = options[:section] || 'generic'
11
+ target = options[:on]
12
+ acl = options[:acl] || "#{privilege.active_acl_description}"
13
+ ActiveAcl::Acl.transaction do
14
+ unless acl.kind_of?(ActiveAcl::Acl)
15
+ case section
16
+ when String
17
+ section = ActiveAcl::AclSection.find_or_create_by_iname(section)
18
+ when Hash
19
+ section = ActiveAcl::AclSection.create(section)
20
+ #else section should be an ActiveAcl::AclSection
21
+ end
22
+ section.save! if section.new_record?
23
+ end
24
+
25
+ case acl
26
+ when String
27
+ acl=ActiveAcl::Acl.find_or_create_by_iname(acl)
28
+ acl.section=section unless acl.section
29
+ when Hash
30
+ acl=ActiveAcl::Acl.create(acl.merge({:section => section}))
31
+ end
32
+ acl.save! if acl.new_record?
33
+
34
+ acl.privileges << privilege
35
+ if ActiveAcl.is_access_group?(self.class)
36
+ acl.requester_groups << self unless acl.requester_groups.include?(self)
37
+ else
38
+ acl.requesters << self unless acl.requesters.include?(self)
39
+ end
40
+ if target
41
+ if ActiveAcl.is_access_group?(target.class) && !options[:target_as_object]
42
+ acl.target_groups << target unless acl.target_groups.include?(target)
43
+ else
44
+ acl.targets << target unless acl.targets.include?(target)
45
+ end
46
+ end
47
+ active_acl_clear_cache! if ActiveAcl.is_access_object?(self.class)
48
+ end
49
+ acl
50
+ end
51
+ end #module
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveAcl
2
+ module Acts #:nodoc:
3
+ module AccessGroup #:nodoc:
4
+ class NestedSet #:nodoc:
5
+ attr_reader :left_column,:right_column
6
+ def initialize(options)
7
+
8
+ @left_column = options[:left_column] || :lft
9
+ @right_column = options[:right_column] || :rgt
10
+ # :controller => ActiveAcl::OPTIONS[:default_group_selector_controller],
11
+ # :action => ActiveAcl::OPTIONS[:default_group_selector_action]}
12
+
13
+ end
14
+ def group_sql(object_handler,target = false)
15
+ target_requester = (target ? 'target' : 'requester')
16
+ if object_handler.habtm?
17
+ "(SELECT DISTINCT g2.id FROM #{object_handler.join_table} ml
18
+ LEFT JOIN #{object_handler.group_table_name} g1 ON ml.#{object_handler.association_foreign_key} = g1.id CROSS JOIN #{object_handler.group_table_name} g2
19
+ WHERE ml.#{object_handler.foreign_key} = %{#{target_requester}_id} AND (g2.#{left_column} <= g1.#{left_column} AND g2.#{right_column} >= g1.#{right_column}))"
20
+ else
21
+ "(SELECT DISTINCT g2.id FROM #{object_handler.group_table_name} g1 CROSS JOIN #{object_handler.group_table_name} g2
22
+ WHERE g1.id = %{#{target_requester}_group_id} AND (g2.#{left_column} <= g1.#{left_column} AND g2.#{right_column} >= g1.#{right_column}))"
23
+ end
24
+ #"r_groups.#{left_column} - r_groups.#{right_column} ASC"
25
+ end
26
+ def order_by(object_handler,target=false)
27
+ target_requester = (target ? 't' : 'r')
28
+ "#{target_requester}_groups.#{left_column} - #{target_requester}_groups.#{right_column} ASC"
29
+ end
30
+ end #class
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,253 @@
1
+ module ActiveAcl #:nodoc:
2
+ module Acts #:nodoc:
3
+ module AccessObject #:nodoc:
4
+
5
+ # handels grouped objects
6
+ # the group is a nested_set
7
+ class ObjectHandler #:nodoc:
8
+ attr_reader :klass,:group_class_name,:join_table,:group_table_name,
9
+ :foreign_key,:association_foreign_key,:group_handler
10
+ def initialize(klass,options={})
11
+ @klass = klass
12
+ if options[:grouped_by]
13
+ @group_class_name = options[:grouped_by].to_s.classify
14
+ @group_handler=ActiveAcl.group_handler(@group_class_name.constantize)
15
+ @group_table_name=@group_class_name.constantize.table_name
16
+ @join_table = options[:join_table] || [klass.name.pluralize.underscore.gsub(/\//,'_'), group_class_name.pluralize.underscore.gsub(/\//,'_')].sort.join('_')
17
+ @foreign_key = options[:foreign_key] || "#{klass.name.demodulize.underscore}_id"
18
+ @association_foreign_key = options[:association_foreign_key] || "#{group_class_name.demodulize.underscore}_id"
19
+ @habtm = options[:habtm] || (options[:grouped_by].to_s.demodulize.singularize != options[:grouped_by].to_s.demodulize)
20
+ end
21
+
22
+ logger=Rails.logger
23
+ logger.debug "ActiveAcl: registered ObjectHandler for #{klass}"
24
+ logger.debug "grouped: #{self.grouped?}, habtm: #{habtm?}"
25
+ #set the SQL fragments
26
+ prepare_requester_sql
27
+ prepare_target_sql
28
+ end
29
+ def habtm?
30
+ @habtm
31
+ end
32
+ def grouped?
33
+ !!@group_class_name
34
+ end
35
+
36
+ def klass_name
37
+ klass.base_class.name
38
+ end
39
+
40
+ #checks the privilege of a requester on a target (optional)
41
+ def has_privilege?(requester,privilege,target=nil)
42
+ value = get_cached(requester,privilege,target)
43
+
44
+ return value unless value.nil? #got the cached and return
45
+ #todo check cash l2
46
+
47
+ vars={'requester_id' => requester.id}
48
+ vars['requester_group_id'] = requester.send(association_foreign_key) if !self.habtm? && self.grouped?
49
+ sql = ''
50
+ sql << query_r_select
51
+ if target
52
+ t_handler=target.active_acl_handler
53
+
54
+ sql << t_handler.query_t_select
55
+ sql << "\n WHERE "
56
+ sql << query_r_where_3d
57
+ sql << t_handler.query_t_where
58
+ sql << "\n ORDER BY "
59
+
60
+ #TODO: ordering is a mess (use an array?)
61
+ order = (grouped? ? order_by_3d.dup : [])
62
+ if t_handler.grouped?
63
+ order << "(CASE WHEN t_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC"
64
+ order << t_handler.group_handler.order_by(target,true)
65
+ vars['target_group_id'] = target.send(t_handler.association_foreign_key) unless t_handler.habtm?
66
+ end
67
+ order << 'acls.updated_at DESC'
68
+ sql << order.join(',')
69
+
70
+ sql << " LIMIT 1"
71
+ vars['privilege_id'] = privilege.id
72
+ vars['target_id'] = target.id
73
+ vars['target_type'] = target.class.base_class.name
74
+ else
75
+ sql << " WHERE "
76
+ sql << query_r_where_2d
77
+ sql << "\n ORDER BY "
78
+ sql << order_by_2d
79
+ end
80
+
81
+ #replacing the vars in the SQL
82
+ sql=sql.gsub(/%{[^}]+}/) do |var|
83
+ vars[var[2..-2]] || var
84
+ end
85
+
86
+ results = ActiveAcl::OPTIONS[:db].query(sql) #get the query from the db
87
+ value=set_cached(requester,privilege,target,results)
88
+ return value
89
+ end
90
+ #gets the instance cache from the background store or a hash
91
+ def get_instance_cache(requester)
92
+ cache.get(requester_cache_id(requester)) || {}
93
+ end
94
+ #destroy the 2nd level cache
95
+ def delete_cached(requester)
96
+ cache.delete(requester_cache_id(requester))
97
+ end
98
+
99
+ attr_reader :query_t_select,:query_t_where
100
+
101
+ #Things go private from here ----------------
102
+ private
103
+ def cache
104
+ ActiveAcl::OPTIONS[:cache]
105
+ end
106
+
107
+ #builds a instance_cache key for a query
108
+ def query_id(requester,privilege,target)
109
+ privilege_id = (privilege.kind_of?(ActiveAcl::Privilege) ? privilege.id : privilege)
110
+ [privilege_id, klass.base_class.name, requester.id, (target ? target.class.base_class.name : ''), (target ? target.id.to_s : '')].join('-')
111
+ end
112
+
113
+ #builds the cache key for a requester for beackground cache
114
+ def requester_cache_id(requester)
115
+ 'active_acl_instance-' + klass.base_class.name + '-' + requester.id.to_s
116
+ end
117
+
118
+ # Caching is done on different levels:
119
+ # Requesting a 2d privilege should fill the instance cache with all 2d privileges
120
+ # Requesting a 3d should be stored in the instance cache
121
+ # changing the instance cache stores it to the backstore (if any exists)
122
+ def get_cached(requester,privilege,target)
123
+
124
+ instance_cache=requester.active_acl_instance_cache
125
+ q_id=query_id(requester,privilege,target)
126
+ # try to get from instance cache
127
+ if (value=instance_cache[q_id]).nil? #cache miss?
128
+ if target.nil? && requester.active_acl_cached_2d?
129
+ Rails.logger.debug 'ACTIVE_ACL::INSTANCE_CACHE::DENY ' + q_id
130
+ return false #it should be cached but it's not there: DENY
131
+ else
132
+ return nil #we don't cache all 3d acl: DB LOOKUP
133
+ end
134
+ else #found in cache: return the results
135
+ Rails.logger.debug 'ACTIVE_ACL::INSTANCE_CACHE::' + (value ? 'GRANT ' : 'DENY ') + q_id
136
+ return value
137
+ end
138
+ nil
139
+ end
140
+
141
+ def set_cached(requester,privilege,target,results)
142
+
143
+ this_query_id=query_id(requester,privilege,target)
144
+ instance_cache=requester.active_acl_instance_cache
145
+
146
+ if target.nil? #no target? then results are all 2d privileges of the requester
147
+ last_privilege_value = nil
148
+ results.each do |row|
149
+ if row['privilege_id'] != last_privilege_value
150
+ last_privilege_value = row['privilege_id']
151
+ q_id=query_id(requester,last_privilege_value,nil)
152
+ #TODO: put the true comparison into the db handler
153
+ v=((row['allow'] == '1') or (row['allow'] == 't'))
154
+ instance_cache[q_id] = v
155
+ end
156
+ end
157
+ requester.active_acl_cached_2d! #mark the cache as filled (at least 2d)
158
+ # the result should be in the cache now or we return false
159
+ value=instance_cache[this_query_id] || false
160
+ else #3d request?
161
+ if results.empty?
162
+ value=false
163
+ instance_cache[this_query_id] = value
164
+ else #3d and a hit
165
+ value = ((results[0]['allow'].to_s == '1') or (results[0]['allow'].to_s == 't'))
166
+ instance_cache[this_query_id] = value
167
+ end
168
+ end
169
+ raise "something went realy wrong!" if value.nil?
170
+
171
+ #cache the whole instance cache
172
+ cache.set(requester_cache_id(requester),instance_cache,ActiveAcl::OPTIONS[:cache_privilege_timeout])
173
+
174
+ value
175
+ end
176
+
177
+ # build ACL query strings once,
178
+ # so we don't need to do this on every request
179
+ # SQL:
180
+ # we always need acl,and privileges, and requester_links
181
+ # we need the target_links if its a 3d query
182
+ # we need the target_groups if the it's a 3d query and the target is grouped
183
+ # we need the requester_groups if the requester is grouped
184
+ # the ordering depens on 2d/3d
185
+ # We'll build the SQL on demand and cache it so it'll
186
+ # be a function of: requester,target,privilege
187
+ attr_reader :query_r_select, :query_r_where_2d, :query_r_where_3d, :order_by_3d,:order_by_2d
188
+ def prepare_requester_sql
189
+ @query_r_select = <<-QUERY
190
+ SELECT acls.id, acls.allow, privileges.id AS privilege_id FROM #{ActiveAcl::OPTIONS[:acls_table]} acls
191
+ LEFT JOIN #{ActiveAcl::OPTIONS[:acls_privileges_table]} acls_privileges ON acls_privileges.acl_id=acls.id
192
+ LEFT JOIN #{ActiveAcl::OPTIONS[:privileges_table]} privileges ON privileges.id = acls_privileges.privilege_id
193
+ LEFT JOIN #{ActiveAcl::OPTIONS[:requester_links_table]} r_links ON r_links.acl_id=acls.id
194
+ QUERY
195
+ if grouped?
196
+ requester_groups_table = group_class_name.constantize.table_name
197
+ requester_group_type = group_class_name.constantize.name
198
+
199
+ @query_r_select << "
200
+ LEFT JOIN #{ActiveAcl::OPTIONS[:requester_group_links_table]} r_g_links ON acls.id = r_g_links.acl_id AND r_g_links.requester_group_type = '#{requester_group_type}'
201
+ LEFT JOIN #{requester_groups_table} r_groups ON r_g_links.requester_group_id = r_groups.id
202
+ "
203
+ end
204
+
205
+ @query_r_where_3d = "acls.enabled = #{klass.connection.quote(true)} AND (privileges.id = %{privilege_id}) "
206
+ @query_r_where_2d = "acls.enabled = #{klass.connection.quote(true)}"
207
+ query = " AND ((r_links.requester_id=%{requester_id}
208
+ AND r_links.requester_type='#{klass.base_class.name}')"
209
+ if grouped?
210
+
211
+ query << " OR (r_g_links.requester_group_id IN #{group_handler.group_sql(self)})) "
212
+ else
213
+ query << ")"
214
+ end
215
+ @query_r_where_3d << query
216
+ @query_r_where_2d << query
217
+
218
+
219
+ #@query_r_where_2d << '(t_g_links.acl_id IS NULL)) '
220
+ @order_by_3d = []
221
+ @order_by_3d << "(CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC"
222
+ @order_by_3d << group_handler.order_by(self) if grouped?
223
+
224
+
225
+
226
+ #TODO ordering of groups
227
+ @order_by_2d = 'privileges.id,'
228
+ @order_by_2d << "(CASE WHEN r_g_links.acl_id IS NULL THEN 0 ELSE 1 END) ASC," if grouped?
229
+ @order_by_2d << "acls.updated_at DESC"
230
+ end
231
+
232
+ def prepare_target_sql
233
+ @query_t_select = " LEFT JOIN #{ActiveAcl::OPTIONS[:target_links_table]} t_links ON t_links.acl_id=acls.id"
234
+ if grouped?
235
+ target_groups_table = @group_class_name.constantize.table_name
236
+ target_group_type = @group_class_name.constantize.name
237
+
238
+ @query_t_select << " LEFT JOIN #{ActiveAcl::OPTIONS[:target_group_links_table]} t_g_links ON t_g_links.acl_id=acls.id
239
+ AND t_g_links.target_group_type = '#{target_group_type}'
240
+ LEFT JOIN #{target_groups_table} t_groups ON t_groups.id=t_g_links.target_group_id"
241
+ end
242
+ @query_t_where = " AND ((t_links.target_id=%{target_id}
243
+ AND t_links.target_type = '%{target_type}' )"
244
+ if grouped?
245
+ @query_t_where << " OR t_g_links.target_group_id IN #{group_handler.group_sql(self,true)})"
246
+ else
247
+ @query_t_where << ")"
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end