stffn-declarative_authorization 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/CHANGELOG +9 -0
  2. data/README.rdoc +22 -6
  3. data/app/controllers/authorization_rules_controller.rb +135 -14
  4. data/app/helpers/authorization_rules_helper.rb +96 -13
  5. data/app/views/authorization_rules/_change.erb +49 -0
  6. data/app/views/authorization_rules/_show_graph.erb +37 -0
  7. data/app/views/authorization_rules/_suggestion.erb +9 -0
  8. data/app/views/authorization_rules/_suggestions.erb +24 -0
  9. data/app/views/authorization_rules/change.html.erb +124 -0
  10. data/app/views/authorization_rules/graph.dot.erb +23 -4
  11. data/app/views/authorization_rules/graph.html.erb +1 -0
  12. data/app/views/authorization_rules/index.html.erb +3 -2
  13. data/app/views/authorization_usages/index.html.erb +2 -11
  14. data/config/routes.rb +2 -1
  15. data/lib/declarative_authorization/authorization.rb +87 -35
  16. data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
  17. data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
  18. data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
  19. data/lib/declarative_authorization/development_support/development_support.rb +243 -0
  20. data/lib/declarative_authorization/helper.rb +6 -2
  21. data/lib/declarative_authorization/in_controller.rb +254 -26
  22. data/lib/declarative_authorization/in_model.rb +27 -2
  23. data/lib/declarative_authorization/maintenance.rb +22 -8
  24. data/lib/declarative_authorization/obligation_scope.rb +14 -9
  25. data/lib/declarative_authorization/reader.rb +10 -2
  26. data/test/authorization_test.rb +44 -0
  27. data/test/controller_filter_resource_access_test.rb +385 -0
  28. data/test/controller_test.rb +14 -6
  29. data/test/helper_test.rb +21 -0
  30. data/test/maintenance_test.rb +26 -0
  31. data/test/model_test.rb +28 -0
  32. data/test/test_helper.rb +14 -1
  33. metadata +15 -5
  34. data/lib/declarative_authorization/authorization_rules_analyzer.rb +0 -138
  35. data/test/authorization_rules_analyzer_test.rb +0 -123
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ * Simplified controller authorization with filter_resource_access [sb]
2
+
3
+ * Allow passing explicit context in addition to object in permitted_to? [Olly Lylo, sb]
4
+
5
+ * Change Supporter: suggest changes to authorization rules [sb]
6
+
7
+ * Added permitted_to!/? in model [Eike Carls]
8
+
9
+ * New test helper: should_(not_)_be_allowed_to(privilege, object_or_context) [sb]
1
10
 
2
11
  ** RELEASE 0.3 (April 20, 2009) **
3
12
 
data/README.rdoc CHANGED
@@ -79,12 +79,26 @@ generated yourself or at http://www.tzi.org/~sbartsch/declarative_authorization
79
79
 
80
80
  == Controller
81
81
 
82
- If authentication is in place, enabling user-specific access control may be
83
- as simple as one call to filter_access_to :all which simply requires the
84
- according privileges for present actions. E.g. the privilege index_users is
85
- required for action index. This works as a first default configuration
86
- for RESTful controllers, with these privileges easily handled in the
87
- authorization configuration, which will be described below.
82
+ If authentication is in place, there are two ways to enable user-specific
83
+ access control on controller actions. For resource controllers, which more
84
+ or less follow the CRUD pattern, +filter_resource_access+ is the simplest
85
+ approach. It sets up instance variables in before filters and calls
86
+ filter_access_to with the appropriate parameters to protect the CRUD methods.
87
+
88
+ class EmployeesController < ApplicationController
89
+ filter_resource_access
90
+ ...
91
+ end
92
+
93
+ See Authorization::AuthorizationInController::ClassMethods for options on
94
+ nested resources and custom member and collection actions.
95
+
96
+ If you prefer less magic or your controller has no resemblance with the resource
97
+ controllers, directly calling filter_access_to may be the better option. Examples
98
+ are given in the following. E.g. the privilege index users is required for
99
+ action index. This works as a first default configuration for RESTful
100
+ controllers, with these privileges easily handled in the authorization
101
+ configuration, which will be described below.
88
102
 
89
103
  class EmployeesController < ApplicationController
90
104
  filter_access_to :all
@@ -473,10 +487,12 @@ sbartsch at tzi.org
473
487
  = Contributors
474
488
 
475
489
  Thanks to
490
+ * Eike Carls
476
491
  * Erik Dahlstrand
477
492
  * Jeremy Friesen
478
493
  * Brian Langenfeld
479
494
  * Geoff Longman
495
+ * Olly Lylo
480
496
  * Mark Mansour
481
497
  * Mike Vincent
482
498
 
@@ -1,6 +1,8 @@
1
1
  if Authorization::activate_authorization_rules_browser?
2
2
 
3
- require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization authorization_rules_analyzer})
3
+ require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization development_support analyzer})
4
+ require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization development_support change_supporter})
5
+ require File.join(File.dirname(__FILE__), %w{.. .. lib declarative_authorization development_support development_support})
4
6
 
5
7
  begin
6
8
  # for nice auth_rules output:
@@ -28,29 +30,105 @@ class AuthorizationRulesController < ApplicationController
28
30
  end
29
31
  end
30
32
 
33
+ def change
34
+ @users = find_all_users
35
+ @users.sort! {|a, b| a.login <=> b.login }
36
+
37
+ @privileges = authorization_engine.auth_rules.collect {|rule| rule.privileges.to_a}.flatten.uniq
38
+ @privileges = @privileges.collect {|priv| Authorization::DevelopmentSupport::AnalyzerEngine::Privilege.for_sym(priv, authorization_engine).descendants.map(&:to_sym) }.flatten.uniq
39
+ @privileges.sort_by {|priv| priv.to_s}
40
+ @privilege = params[:privilege].to_sym rescue @privileges.first
41
+ @contexts = authorization_engine.auth_rules.collect {|rule| rule.contexts.to_a}.flatten.uniq
42
+ @context = params[:context].to_sym rescue @contexts.first
43
+
44
+ respond_to do |format|
45
+ format.html
46
+ format.js do
47
+ render :partial => 'change'
48
+ end
49
+ end
50
+ end
51
+
52
+ def suggest_change
53
+ users_permission = params[:user].inject({}) do |memo, (user_id, data)|
54
+ if data[:permission] != "undetermined"
55
+ begin
56
+ memo[find_user_by_id(user_id)] = (data[:permission] == 'yes')
57
+ rescue ActiveRecord::NotFound
58
+ end
59
+ end
60
+ memo
61
+ end
62
+
63
+ prohibited_actions = (params[:prohibited_action] || []).collect do |spec|
64
+ deserialize_changes(spec).flatten
65
+ end
66
+
67
+ users_keys = users_permission.keys
68
+ analyzer = Authorization::DevelopmentSupport::ChangeSupporter.new(authorization_engine)
69
+
70
+ privilege = params[:privilege].to_sym
71
+ context = params[:context].to_sym
72
+ @context = context
73
+ @approaches = analyzer.find_approaches_for(:users => users_keys, :prohibited_actions => prohibited_actions) do
74
+ users.each_with_index do |user, idx|
75
+ args = [privilege, {:context => context, :user => user}]
76
+ assert(users_permission[users_keys[idx]] ? permit?(*args) : !permit?(*args))
77
+ end
78
+ end
79
+
80
+ respond_to do |format|
81
+ format.js do
82
+ render :partial => 'suggestions'
83
+ end
84
+ end
85
+ end
86
+
31
87
  private
32
88
  def auth_to_dot (options = {})
33
89
  options = {
34
90
  :effective_role_privs => true,
35
91
  :privilege_hierarchy => false,
92
+ :stacked_roles => false,
36
93
  :only_relevant_contexts => true,
94
+ :only_relevant_roles => false,
37
95
  :filter_roles => nil,
38
96
  :filter_contexts => nil,
39
- :highlight_privilege => nil
97
+ :highlight_privilege => nil,
98
+ :changes => nil,
99
+ :users => nil
40
100
  }.merge(options)
41
101
 
102
+ @has_changes = options[:changes] && !options[:changes].empty?
42
103
  @highlight_privilege = options[:highlight_privilege]
43
- @roles = authorization_engine.roles
44
- @roles = @roles.select {|r| r == options[:filter_roles] } if options[:filter_roles]
45
- @role_hierarchy = authorization_engine.role_hierarchy
46
- @privilege_hierarchy = authorization_engine.privilege_hierarchy
104
+ @stacked_roles = options[:stacked_roles]
105
+
106
+ @users = options[:users]
107
+
108
+ engine = authorization_engine.clone
109
+ @changes = replay_changes(engine, @users, options[:changes]) if options[:changes]
110
+
111
+ options[:filter_roles] ||= @users.collect {|user| user.role_symbols}.flatten.uniq if options[:only_relevant_roles] and @users
112
+
113
+ filter_roles_flattened = nil
114
+ if options[:filter_roles]
115
+ filter_roles_flattened = options[:filter_roles].collect do |role_sym|
116
+ Authorization::DevelopmentSupport::AnalyzerEngine::Role.for_sym(role_sym, engine).
117
+ ancestors.map(&:to_sym) + [role_sym]
118
+ end.flatten.uniq
119
+ end
120
+
121
+ @roles = engine.roles
122
+ @roles = @roles.select {|r| filter_roles_flattened.include?(r) } if options[:filter_roles]
123
+ @role_hierarchy = engine.role_hierarchy
124
+ @privilege_hierarchy = engine.privilege_hierarchy
47
125
 
48
- @contexts = authorization_engine.auth_rules.
126
+ @contexts = engine.auth_rules.
49
127
  collect {|ar| ar.contexts.to_a}.flatten.uniq
50
128
  @contexts = @contexts.select {|c| c == options[:filter_contexts] } if options[:filter_contexts]
51
129
  @context_privs = {}
52
130
  @role_privs = {}
53
- authorization_engine.auth_rules.each do |auth_rule|
131
+ engine.auth_rules.each do |auth_rule|
54
132
  @role_privs[auth_rule.role] ||= []
55
133
  auth_rule.contexts.
56
134
  select {|c| options[:filter_contexts].nil? or c == options[:filter_contexts]}.
@@ -64,15 +142,23 @@ class AuthorizationRulesController < ApplicationController
64
142
 
65
143
  if options[:effective_role_privs]
66
144
  @roles.each do |role|
67
- @role_privs[role] ||= []
68
- (@role_hierarchy[role] || []).each do |lower_role|
69
- @role_privs[role].concat(@role_privs[lower_role]).uniq!
145
+ role = Authorization::DevelopmentSupport::AnalyzerEngine::Role.for_sym(role, engine)
146
+ @role_privs[role.to_sym] ||= []
147
+ role.ancestors.each do |lower_role|
148
+ @role_privs[role.to_sym].concat(@role_privs[lower_role.to_sym]).uniq!
70
149
  end
71
150
  end
72
151
  end
73
152
 
153
+ @roles.delete_if do |role|
154
+ role = Authorization::DevelopmentSupport::AnalyzerEngine::Role.for_sym(role, engine)
155
+ ([role] + role.ancestors).all? {|inner_role| @role_privs[inner_role.to_sym].blank? }
156
+ end
157
+
74
158
  if options[:only_relevant_contexts]
75
- @contexts.delete_if {|context| @roles.all? {|role| !@role_privs[role] || !@role_privs[role].any? {|info| info[0] == context}}}
159
+ @contexts.delete_if do |context|
160
+ @roles.all? {|role| !@role_privs[role] || !@role_privs[role].any? {|info| info[0] == context}}
161
+ end
76
162
  end
77
163
 
78
164
  if options[:privilege_hierarchy]
@@ -88,6 +174,20 @@ class AuthorizationRulesController < ApplicationController
88
174
 
89
175
  render_to_string :template => 'authorization_rules/graph.dot.erb', :layout => false
90
176
  end
177
+
178
+ def replay_changes (engine, users, changes)
179
+ changes.inject({}) do |memo, info|
180
+ case info[0]
181
+ when :add_privilege, :add_role
182
+ Authorization::DevelopmentSupport::AnalyzerEngine.apply_change(engine, info)
183
+ when :assign_role_to_user
184
+ user = users.find {|u| u.login == info[2]}
185
+ user.role_symbols << info[1] if user
186
+ end
187
+ (memo[info[0]] ||= Set.new) << info[1..-1]
188
+ memo
189
+ end
190
+ end
91
191
 
92
192
  def dot_to_svg (dot_data)
93
193
  gv = IO.popen("#{Authorization.dot_path} -q -Tsvg", "w+")
@@ -102,11 +202,32 @@ class AuthorizationRulesController < ApplicationController
102
202
  {
103
203
  :effective_role_privs => !params[:effective_role_privs].blank?,
104
204
  :privilege_hierarchy => !params[:privilege_hierarchy].blank?,
105
- :filter_roles => params[:filter_roles].blank? ? nil : params[:filter_roles].to_sym,
205
+ :stacked_roles => !params[:stacked_roles].blank?,
206
+ :only_relevant_roles => !params[:only_relevant_roles].blank?,
207
+ :filter_roles => params[:filter_roles].blank? ? nil : (params[:filter_roles].is_a?(Array) ? params[:filter_roles].map(&:to_sym) : [params[:filter_roles].to_sym]),
106
208
  :filter_contexts => params[:filter_contexts].blank? ? nil : params[:filter_contexts].to_sym,
107
- :highlight_privilege => params[:highlight_privilege].blank? ? nil : params[:highlight_privilege].to_sym
209
+ :highlight_privilege => params[:highlight_privilege].blank? ? nil : params[:highlight_privilege].to_sym,
210
+ :changes => deserialize_changes(params[:changes]),
211
+ :users => params[:user_ids] && params[:user_ids].collect {|user_id| find_user_by_id(user_id)}
108
212
  }
109
213
  end
214
+
215
+ def deserialize_changes (changes)
216
+ if changes
217
+ changes.split(';').collect do |info|
218
+ info.split(',').collect do |info_part|
219
+ info_part[0,1] == ':' ? info_part[1..-1].to_sym : info_part
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ def find_user_by_id (id)
226
+ User.find(id)
227
+ end
228
+ def find_all_users
229
+ User.all.select {|user| !user.login.blank?}
230
+ end
110
231
  end
111
232
 
112
233
  else
@@ -3,8 +3,8 @@ module AuthorizationRulesHelper
3
3
  regexps = {
4
4
  :constant => [/(:)(\w+)/],
5
5
  :proc => ['role', 'authorization', 'privileges'],
6
- :statement => ['has_permission_on', 'if_attribute', 'includes', 'privilege', 'to'],
7
- :operator => ['is', 'contains'],
6
+ :statement => ['has_permission_on', 'if_attribute', 'if_permitted_to', 'includes', 'privilege', 'to'],
7
+ :operator => ['is', 'contains', 'is_in', 'is_not', 'is_not_in', 'intersects'],
8
8
  :special => ['user', 'true', 'false'],
9
9
  :preproc => ['do', 'end', /()(=&gt;)/, /()(\{)/, /()(\})/, /()(\[)/, /()(\])/],
10
10
  :comment => [/()(#.*$)/]#,
@@ -23,16 +23,17 @@ module AuthorizationRulesHelper
23
23
  end
24
24
 
25
25
  def policy_analysis_hints (marked_up, policy_data)
26
- analyzer = Authorization::Analyzer.new(controller.authorization_engine)
26
+ analyzer = Authorization::DevelopmentSupport::Analyzer.new(controller.authorization_engine)
27
27
  analyzer.analyze(policy_data)
28
28
  marked_up_by_line = marked_up.split("\n")
29
29
  reports_by_line = analyzer.reports.inject({}) do |memo, report|
30
- memo[report.line] ||= []
31
- memo[report.line] << report
30
+ memo[report.line || 1] ||= []
31
+ memo[report.line || 1] << report
32
32
  memo
33
33
  end
34
34
  reports_by_line.each do |line, reports|
35
- note = %Q{<span class="note" title="#{reports.first.type}: #{reports.first.message}">[i]</span>}
35
+ text = reports.collect {|report| "#{report.type}: #{report.message}"} * " "
36
+ note = %Q{<span class="note" title="#{h text}">[i]</span>}
36
37
  marked_up_by_line[line - 1] = note + marked_up_by_line[line - 1]
37
38
  end
38
39
  marked_up_by_line * "\n"
@@ -45,6 +46,7 @@ module AuthorizationRulesHelper
45
46
 
46
47
  def navigation
47
48
  link_to("Rules", authorization_rules_path) << ' | ' <<
49
+ link_to("Change Supporter", change_authorization_rules_path) << ' | ' <<
48
50
  link_to("Graphical view", graph_authorization_rules_path) << ' | ' <<
49
51
  link_to("Usages", authorization_usages_path) #<< ' | ' <<
50
52
  # 'Edit | ' <<
@@ -52,20 +54,101 @@ module AuthorizationRulesHelper
52
54
  end
53
55
 
54
56
  def role_color (role, fill = false)
55
- fill_colors = %w{#ffdddd #ddffdd #ddddff #ffffdd #ffddff #ddffff}
56
- colors = %w{#dd0000 #00dd00 #0000dd #dddd00 #dd00dd #00dddd}
57
- @@role_colors ||= {}
58
- @@role_colors[role] ||= begin
59
- idx = @@role_colors.length % colors.length
60
- [colors[idx], fill_colors[idx]]
57
+ if @has_changes
58
+ if has_changed(:add_role, role)
59
+ fill ? '#ddffdd' : '#000000'
60
+ elsif has_changed(:remove_role, role)
61
+ fill ? '#ffdddd' : '#000000'
62
+ else
63
+ fill ? '#ddddff' : '#000000'
64
+ end
65
+ else
66
+ fill_colors = %w{#ffdddd #ddffdd #ddddff #ffffdd #ffddff #ddffff}
67
+ colors = %w{#dd0000 #00dd00 #0000dd #dddd00 #dd00dd #00dddd}
68
+ @@role_colors ||= {}
69
+ @@role_colors[role] ||= begin
70
+ idx = @@role_colors.length % colors.length
71
+ [colors[idx], fill_colors[idx]]
72
+ end
73
+ @@role_colors[role][fill ? 1 : 0]
61
74
  end
62
- @@role_colors[role][fill ? 1 : 0]
63
75
  end
64
76
 
65
77
  def role_fill_color (role)
66
78
  role_color(role, true)
67
79
  end
68
80
 
81
+ def privilege_color (privilege, context, role)
82
+ has_changed(:add_privilege, privilege, context, role) ? '#00dd00' :
83
+ (has_changed(:remove_privilege, privilege, context, role) ? '#dd0000' :
84
+ role_color(role))
85
+ end
86
+
87
+ def describe_step (step, options = {})
88
+ options = {:with_removal => false}.merge(options)
89
+
90
+ case step[0]
91
+ when :add_privilege
92
+ dont_assign = prohibit_link(step[0,3],
93
+ "Add privilege <strong>#{h step[1].to_sym.inspect} #{h step[2].to_sym.inspect}</strong> to any role",
94
+ "Don't suggest adding #{h step[1].to_sym.inspect} #{h step[2].to_sym.inspect}.", options)
95
+ "Add privilege <strong>#{h step[1].inspect} #{h step[2].inspect}</strong>#{dont_assign} to role <strong>#{h step[3].to_sym.inspect}</strong>"
96
+ when :remove_privilege
97
+ dont_remove = prohibit_link(step[0,3],
98
+ "Remove privilege <strong>#{h step[1].to_sym.inspect} #{h step[2].to_sym.inspect}</strong> from any role",
99
+ "Don't suggest removing #{h step[1].to_sym.inspect} #{h step[2].to_sym.inspect}.", options)
100
+ "Remove privilege <strong>#{h step[1].inspect} #{h step[2].inspect}</strong>#{dont_remove} from role <strong>#{h step[3].to_sym.inspect}</strong>"
101
+ when :add_role
102
+ "New role <strong>#{h step[1].to_sym.inspect}</strong>"
103
+ when :assign_role_to_user
104
+ dont_assign = prohibit_link(step[0,2],
105
+ "Assign role <strong>#{h step[1].to_sym.inspect}</strong> to any user",
106
+ "Don't suggest assigning #{h step[1].to_sym.inspect}.", options)
107
+ "Assign role <strong>#{h step[1].to_sym.inspect}</strong>#{dont_assign} to <strong>#{h readable_step_info(step[2])}</strong>"
108
+ when :remove_role_from_user
109
+ dont_remove = prohibit_link(step[0,2],
110
+ "Remove role <strong>#{h step[1].to_sym.inspect}</strong> from any user",
111
+ "Don't suggest removing #{h step[1].to_sym.inspect}.", options)
112
+ "Remove role <strong>#{h step[1].to_sym.inspect}</strong>#{dont_remove} from <strong>#{h readable_step_info(step[2])}</strong>"
113
+ else
114
+ step.collect {|info| readable_step_info(info) }.map {|str| h str } * ', '
115
+ end + prohibit_link(step, options[:with_removal] ? "#{escape_javascript(describe_step(step))}" : '',
116
+ "Don't suggest this action.", options)
117
+ end
118
+
119
+ def prohibit_link (step, text, title, options)
120
+ options[:with_removal] ?
121
+ ' ' + link_to_function("[x]", "prohibit_action('#{serialize_action(step)}', '#{text}')",
122
+ :class => 'unimportant', :title => title) :
123
+ ''
124
+ end
125
+
126
+ def readable_step_info (info)
127
+ case info
128
+ when Symbol then info.inspect
129
+ when User then info.login
130
+ else info.to_sym.inspect
131
+ end
132
+ end
133
+
134
+ def serialize_changes (approach)
135
+ changes = approach.changes.collect {|step| step.to_a.first.is_a?(Enumerable) ? step.to_a : [step.to_a]}
136
+ changes.collect {|multi_step| multi_step.collect {|step| serialize_action(step) }}.flatten * ';'
137
+ end
138
+
139
+ def serialize_action (step)
140
+ step.collect {|info| readable_step_info(info) } * ','
141
+ end
142
+
143
+ def serialize_relevant_roles (approach)
144
+ {:filter_roles => (Authorization::DevelopmentSupport::AnalyzerEngine.relevant_roles(approach.engine, approach.users).
145
+ map(&:to_sym) + [:new_role_for_change_analyzer]).uniq}.to_param
146
+ end
147
+
148
+ def has_changed (*args)
149
+ @changes && @changes[args[0]] && @changes[args[0]].include?(args[1..-1])
150
+ end
151
+
69
152
  def auth_usage_info_classes (auth_info)
70
153
  classes = []
71
154
  if auth_info[:controller_permissions]
@@ -0,0 +1,49 @@
1
+ <form>
2
+ <h2>1. Choose permission to change</h2>
3
+ <p class="action-options">
4
+ <label>Privilege</label>
5
+ <%= select_tag :privilege, options_for_select(@privileges.map(&:to_s).sort, @privilege.to_s) %>
6
+ <br/>
7
+ <label>On</label>
8
+ <%= select_tag :context, options_for_select(@contexts.map(&:to_s).sort, @context.to_s) %>
9
+ <br/>
10
+ <%= link_to_function "Current permissions", "show_current_permissions()", :class => 'unimportant' %>
11
+ </p>
12
+
13
+ <h2>2. Whose permission should be changed?</h2>
14
+ <table class="change-options">
15
+ <thead>
16
+ <tr>
17
+ <td>User</td>
18
+ <td>Current roles</td>
19
+ <td class="choose">?</td>
20
+ <td class="choose">Yes</td>
21
+ <td class="choose">No</td>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <% @users.each do |user| %>
26
+ <tr class="<%= controller.authorization_engine.permit?(@privilege, :context => @context, :user => user, :skip_attribute_test => true) ? 'permitted' : 'not-permitted' %>">
27
+ <td class="user_id"><%=h user.id %></td>
28
+ <td style="font-weight:bold"><%=h user.login %></td>
29
+ <td><%=h user.role_symbols * ', ' %></td>
30
+ <td><%= radio_button_tag "user[#{user.id}][permission]", "undetermined", true %></td>
31
+ <td class="yes"><%= radio_button_tag "user[#{user.id}][permission]", "yes" %></td>
32
+ <td class="no"><%= radio_button_tag "user[#{user.id}][permission]", "no" %></td>
33
+ </tr>
34
+ <% end %>
35
+ <tr>
36
+ <td colspan="5" style="text-align:right; padding-top:0.5em" class="unimportant">
37
+ <span style="background: #FFE599;">&nbsp; &nbsp;</span> Current permission
38
+ </td>
39
+ </tr>
40
+ </tbody>
41
+ </table>
42
+
43
+ <h2 style="display:none">Prohibited actions</h2>
44
+ <ul id="prohibited_actions"></ul>
45
+
46
+ <p class="submit">
47
+ <%= button_to_function "Suggest Changes", "suggest_changes()" %>
48
+ </p>
49
+ </form>