uhees-declarative_authorization 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +77 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +490 -0
  4. data/Rakefile +43 -0
  5. data/app/controllers/authorization_rules_controller.rb +235 -0
  6. data/app/controllers/authorization_usages_controller.rb +23 -0
  7. data/app/helpers/authorization_rules_helper.rb +183 -0
  8. data/app/views/authorization_rules/_change.erb +49 -0
  9. data/app/views/authorization_rules/_show_graph.erb +37 -0
  10. data/app/views/authorization_rules/_suggestion.erb +9 -0
  11. data/app/views/authorization_rules/_suggestions.erb +24 -0
  12. data/app/views/authorization_rules/change.html.erb +124 -0
  13. data/app/views/authorization_rules/graph.dot.erb +68 -0
  14. data/app/views/authorization_rules/graph.html.erb +40 -0
  15. data/app/views/authorization_rules/index.html.erb +17 -0
  16. data/app/views/authorization_usages/index.html.erb +36 -0
  17. data/authorization_rules.dist.rb +20 -0
  18. data/config/routes.rb +7 -0
  19. data/garlic_example.rb +20 -0
  20. data/init.rb +5 -0
  21. data/lib/declarative_authorization.rb +15 -0
  22. data/lib/declarative_authorization/authorization.rb +630 -0
  23. data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
  24. data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
  25. data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
  26. data/lib/declarative_authorization/development_support/development_support.rb +243 -0
  27. data/lib/declarative_authorization/helper.rb +60 -0
  28. data/lib/declarative_authorization/in_controller.rb +367 -0
  29. data/lib/declarative_authorization/in_model.rb +150 -0
  30. data/lib/declarative_authorization/maintenance.rb +188 -0
  31. data/lib/declarative_authorization/obligation_scope.rb +297 -0
  32. data/lib/declarative_authorization/rails_legacy.rb +14 -0
  33. data/lib/declarative_authorization/reader.rb +438 -0
  34. data/test/authorization_test.rb +823 -0
  35. data/test/controller_test.rb +418 -0
  36. data/test/dsl_reader_test.rb +157 -0
  37. data/test/helper_test.rb +154 -0
  38. data/test/maintenance_test.rb +41 -0
  39. data/test/model_test.rb +1171 -0
  40. data/test/schema.sql +53 -0
  41. data/test/test_helper.rb +103 -0
  42. metadata +104 -0
@@ -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>
@@ -0,0 +1,37 @@
1
+ <% javascript_tag do %>
2
+ function show_graph (privilege, context, user_ids) {
3
+ var params = {
4
+ privilege_hierarchy: 1,
5
+ highlight_privilege: privilege,
6
+ filter_contexts: context
7
+ };
8
+ if (user_ids)
9
+ params['user_ids[]'] = user_ids;
10
+ show_graph_with_params('graph', params);
11
+ }
12
+
13
+ var graph_params = {};
14
+ function show_graph_with_params (graph_id, params) {
15
+ graph_params[graph_id] = params;
16
+ base_url = "<%= graph_authorization_rules_path('svg') %>";
17
+ $(graph_id).data = base_url + '?' + Object.toQueryString(params);
18
+ $(graph_id).up().show();
19
+ }
20
+
21
+ function update_graph_params (graph_id, params) {
22
+ show_graph_with_params(graph_id,
23
+ $H(graph_params[graph_id] || {}).merge(params).toObject());
24
+ }
25
+
26
+ function toggle_graph_params (graph_id) {
27
+ var opts = {}
28
+ $A(arguments).slice(1).each(function (param) {
29
+ opts[param] = graph_params[graph_id][param] ? null : '1';
30
+ });
31
+ update_graph_params(graph_id, opts)
32
+ }
33
+ <% end %>
34
+ <div id="graph-container" style="display:none">
35
+ <%= link_to_function "Hide", "$('graph-container').hide()" %><br/>
36
+ <object id="graph" data="" type="image/svg+xml" style="max-width:100%"/>
37
+ </div>
@@ -0,0 +1,9 @@
1
+ <ul>
2
+ <% suggestion.changes.each do |action| %>
3
+ <% (action.to_a[0].is_a?(Enumerable) ? action.to_a : [action.to_a]).each do |step| %>
4
+ <li>
5
+ <%= describe_step(step.to_a, :with_removal => true) %>
6
+ </li>
7
+ <% end %>
8
+ <% end %>
9
+ </ul>
@@ -0,0 +1,24 @@
1
+ <h2>Suggestions</h2>
2
+
3
+ <% if @approaches.length > 0 %>
4
+ <% if @approaches.first.changes.empty? %>
5
+ <p>No changes necessary.</p>
6
+ <% else %>
7
+ <p>Suggested changes (<%= link_to_function "show", "show_suggest_graph('#{serialize_changes(@approaches.first)}', '#{serialize_relevant_roles(@approaches.first)}', '#{@context}', relevant_user_ids())" %>):</p>
8
+ <%= render "suggestion", :object => @approaches.first %>
9
+ <% end %>
10
+ <% else %>
11
+ <p><strong>No approach found.</strong></p>
12
+ <% end %>
13
+
14
+ <% if @approaches.length > 1 %>
15
+ <p <%= !params[:show_all] ? '' : 'style="display: none"' %>><a href="#" onclick="$(this).up().hide();$('more-suggestions').show();return false">Show other <%= pluralize(@approaches.length - 1, 'approach') %></a></p>
16
+ <div id="more-suggestions" <%= params[:show_all] ? '' : 'style="display: none"' %>>
17
+ <% @approaches[1..-1].each_with_index do |approach, index| %>
18
+ <p>
19
+ <%= (index + 2).ordinalize %> best approach (<%= pluralize(approach.changes.length, 'step') %>, <%= link_to_function "show", "show_suggest_graph('#{serialize_changes(approach)}', '#{serialize_relevant_roles(approach)}', '#{@context}', relevant_user_ids())" %>)
20
+ <%= render "suggestion", :object => approach %>
21
+ </p>
22
+ <% end %>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,124 @@
1
+ <h1>Suggestions on Authorization Rules Change</h1>
2
+ <p><%= navigation %></p>
3
+ <div style="display:none" id="suggest-graph-container">
4
+ <%= link_to_function "Hide", "$(this).up().hide()", :class => 'important' %>
5
+ <%= link_to_function "Toggle stacked roles", "toggle_graph_params('suggest-graph', 'stacked_roles');" %>
6
+ <%= link_to_function "Toggle only users' roles", "toggle_graph_params('suggest-graph', 'only_relevant_roles');" %><br/>
7
+ <object id="suggest-graph" data="" type="image/svg+xml" style="max-width: 98%;max-height: 95%"/>
8
+ </div>
9
+ <%= render 'show_graph' %>
10
+
11
+ <style type="text/css">
12
+ .action-options label { display: block; float: left; width: 7em; padding-bottom: 0.5em }
13
+ .action-options select { float: left; }
14
+ .action-options br { clear: both; }
15
+ .action-options { margin-bottom: 2em }
16
+ .change-options { margin-bottom: 1em }
17
+ .change-options td.user_id { display: none }
18
+ .change-options td { padding-right: 1em; padding-left: 0.5em }
19
+ .change-options thead td { border-bottom: 1px solid lightgrey }
20
+ .change-options thead td.choose { text-align: center; font-weight: bold }
21
+ .change-options tr.permitted td.yes,
22
+ .change-options tr.not-permitted td.no { background: #FFE599 }
23
+ .submit { margin-top: 0 }
24
+ .submit input { font-weight: bold; font-size: 120% }
25
+ #suggest-result {
26
+ position: absolute; left: 60%; right: 10px;
27
+ border-left: 2px solid grey;
28
+ padding-left: 1em;
29
+ z-index: 10;
30
+ }
31
+ #suggest-graph-container {
32
+ background: white; border:1px solid #ccc;
33
+ position:fixed; z-index: 20;
34
+ left:10%; bottom: 10%; right: 10%; top: 10%
35
+ }
36
+ #graph-container {
37
+ background: white; margin: 1em; border:1px solid #ccc;
38
+ max-width:50%; position:fixed; z-index: 20; right:0;
39
+ }
40
+ .unimportant, .remove { color: grey }
41
+ .remove { cursor: pointer }
42
+ </style>
43
+ <% javascript_tag do %>
44
+ function suggest_changes (refresh) {
45
+ if (!refresh)
46
+ $("suggest-result").innerHTML = "Searching...";
47
+ $("suggest-result").show();
48
+ new Ajax.Updater({success: 'suggest-result'}, '<%= suggest_change_authorization_rules_path %>', {
49
+ method: 'get',
50
+ onFailure: function(request) {
51
+ $("suggest-result").innerHTML = "Error while searching."
52
+ },
53
+ parameters: $H($('change').down('form').serialize(true)).merge(refresh ? {show_all: "true"} : {}).toQueryString()
54
+ });
55
+ if (!refresh)
56
+ location.hash = 'suggest-result';
57
+ }
58
+
59
+ function show_suggest_graph (changes, filter_roles_params, filter_context, user_ids) {
60
+ var params = {changes: changes, highlight_privilege: $F('privilege')};
61
+ if (filter_context)
62
+ params['filter_contexts'] = filter_context;
63
+ if (user_ids)
64
+ params['user_ids[]'] = user_ids;
65
+ show_graph_with_params('suggest-graph', params);
66
+ }
67
+
68
+ document.observe('dom:loaded', function() {
69
+ install_change_observers();
70
+ });
71
+
72
+ function install_change_observers () {
73
+ $$('#change select').each(function (el) {
74
+ el.observe('change', function (event) {
75
+ new Ajax.Updater({success: 'change'}, '<%= url_for %>', {
76
+ parameters: { context: $F('context'), privilege: $F('privilege') },
77
+ method: 'get',
78
+ onComplete: function () {
79
+ install_change_observers();
80
+ if ($('graph-container').visible())
81
+ show_current_permissions();
82
+ }
83
+ });
84
+ });
85
+ });
86
+ $('prohibited_actions').observe('click', function (event) {
87
+ var target = event.findElement();
88
+ if (target.hasClassName('remove')) {
89
+ target.up().remove();
90
+ if ($('prohibited_actions').childElements().length == 0)
91
+ $('prohibited_actions').previous().hide();
92
+ suggest_changes();
93
+ }
94
+ });
95
+ }
96
+
97
+ function show_current_permissions () {
98
+ show_graph($F('privilege'), $F('context')/*, relevant_user_ids()*/);
99
+ }
100
+
101
+ function relevant_user_ids () {
102
+ return $$('#change .user_id').collect(function (el) {
103
+ return el.innerHTML;
104
+ }).reject(function (id) {
105
+ return $('user_' + id + '_permission_undetermined').checked;
106
+ });
107
+ }
108
+
109
+ var prohibited_action_template =
110
+ new Template('<li>#{description} <span class="remove">[x]</span><input type="hidden" name="prohibited_action[]" value="#{action}"></li>');
111
+ function prohibit_action (action, description) {
112
+ $('prohibited_actions').previous().show();
113
+ $('prohibited_actions').insert(
114
+ {bottom: prohibited_action_template.evaluate({action: action, description: description}) }
115
+ );
116
+ suggest_changes(true);
117
+ }
118
+ <% end %>
119
+
120
+ <div id="suggest-result" style="display:none"></div>
121
+
122
+ <div id="change">
123
+ <%= render 'change' %>
124
+ </div>
@@ -0,0 +1,68 @@
1
+
2
+ digraph rules {
3
+ compound = true
4
+ edge [arrowhead=open]
5
+ node [shape=box,fontname="sans-serif",fontsize="16"]
6
+ fontname="sans-serif";fontsize="16"
7
+ ranksep = "0.3"
8
+ //concentrate = true
9
+ rankdir = TB
10
+
11
+ <% unless @users.blank? %>
12
+ {
13
+ rank = source
14
+ node [shape=polygon,style=filled,fillcolor="#eeeeee"]
15
+ <% @users.each do |user| %>
16
+ "<%= user.login %>"
17
+ <% end %>
18
+ }
19
+ <% end %>
20
+
21
+ {
22
+ node [shape=ellipse,style=filled]
23
+ <%= @stacked_roles ? '' : "rank = same" %>
24
+ <% @roles.each do |role| %>
25
+ "<%= role.inspect %>" [fillcolor="<%= role_fill_color(role) %>"]
26
+ // ,URL="javascript:set_filter({roles: '<%= role %>'})"
27
+ <% end %>
28
+ <% @roles.each do |role| %>
29
+ <% (@role_hierarchy[role] || []).select {|lower_role| @roles.include?(lower_role)}.each do |lower_role| %>
30
+ "<%= role.inspect %>" -> "<%= lower_role.inspect %>" [arrowhead=empty]
31
+ <% end %>
32
+ <% end %>
33
+ }
34
+
35
+ <% unless @users.blank? %>
36
+ <% @users.each do |user| %>
37
+ <% user.role_symbols.select {|role| @roles.include?(role)}.each do |role| %>
38
+ "<%= user.login %>" -> "<%= role.inspect %>" [color="<%= has_changed(:assign_role_to_user, role, user.login) ? '#00dd00' : (has_changed(:remove_role_from_user, role, user.login) ? '#dd0000' : '#000000') %>"]
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
42
+
43
+ <% @contexts.each do |context| %>
44
+ subgraph cluster_<%= context %> {
45
+ label = "<%= context.inspect %>"
46
+ style=filled; fillcolor="#eeeeee"
47
+ node[fillcolor=white,style=filled]
48
+ <% (@context_privs[context] || []).each do |priv| %>
49
+ <%= priv %>_<%= context %> [label="<%= priv.inspect %>"<%= ',fontcolor="#ff0000"' if @highlight_privilege == priv %>]
50
+ <% end %>
51
+ <% (@context_privs[context] || []).each do |priv| %>
52
+ <% (@privilege_hierarchy[priv] || []).
53
+ select {|p,c| (c.nil? or c == context) and @context_privs[context].include?(p)}.
54
+ each do |lower_priv, c| %>
55
+ <%= priv %>_<%= context %> -> <%= lower_priv %>_<%= context %> [arrowhead=empty]
56
+ <% end %>
57
+ <% end %>
58
+ //read_conferences -> update_conferences [style=invis]
59
+ //create_conferences -> delete_conferences [style=invis]
60
+ }
61
+ <% end %>
62
+
63
+ <% @roles.each do |role| %>
64
+ <% (@role_privs[role] || []).each do |context, privilege, unconditionally, attribute_string| %>
65
+ "<%= role.inspect %>" -> <%= privilege %>_<%= context %> [color="<%= privilege_color(privilege, context, role) %>", minlen=3<%= ", arrowhead=opendot, URL=\"javascript:\", edgetooltip=\"#{attribute_string.gsub('"','')}\"" unless unconditionally %>]
66
+ <% end %>
67
+ <% end %>
68
+ }
@@ -0,0 +1,40 @@
1
+ <h1>Authorization Rules Graph</h1>
2
+ <p>Currently active rules in this application.</p>
3
+ <p><%= navigation %></p>
4
+
5
+ <% javascript_tag do %>
6
+ function update_graph (form) {
7
+ base_url = "<%= url_for :format => 'svg' %>";
8
+ $('graph').data = base_url + '?' + form.serialize();
9
+ }
10
+
11
+ function set_filter (filter) {
12
+ for (f in filter) {
13
+ var select = $("filter_" + f);
14
+ if (select) {
15
+ var opt = select.down("option[value='"+ filter[f] + "']");
16
+ if (opt) {
17
+ opt.selected = true;
18
+ update_graph(select.form);
19
+ }
20
+ }
21
+ }
22
+ }
23
+ <% end %>
24
+ <p>
25
+ <% form_tag do %>
26
+ <%#= link_to_graph "Rules" %>
27
+ <%#= link_to_graph "Privilege hierarchy", :type => 'priv_hierarchy' %>
28
+
29
+ <%= select_tag "filter_roles", options_for_select([["All roles",'']] + controller.authorization_engine.roles.map(&:to_s).sort), :onchange => 'update_graph(this.form)' %>
30
+ <%= select_tag "filter_contexts", options_for_select([["All contexts",'']] + controller.authorization_engine.auth_rules.collect {|ar| ar.contexts.to_a}.flatten.uniq.map(&:to_s).sort), :onchange => 'update_graph(this.form)' %>
31
+ <%= check_box_tag "effective_role_privs", "1", false, :onclick => 'update_graph(this.form)' %> <%= label_tag "effective_role_privs", "Effective privileges" %>
32
+ <%= check_box_tag "privilege_hierarchy", "1", false, :onclick => 'update_graph(this.form)' %> <%= label_tag "privilege_hierarchy", "Show full privilege hierarchy" %>
33
+ <%= check_box_tag "stacked_roles", "1", false, :onclick => 'update_graph(this.form)' %> <%= label_tag "stacked_roles", "Stacked roles" %>
34
+ <% end %>
35
+ </p>
36
+ <div style="margin: 1em;border:1px solid #ccc;max-width:95%">
37
+ <object id="graph" data="<%= url_for :format => 'svg' %>" type="image/svg+xml" style="max-width:100%"/>
38
+ </div>
39
+ <%= button_to_function "Zoom in", '$("graph").style.maxWidth = "";$(this).toggle();$(this).next().toggle()' %>
40
+ <%= button_to_function "Zoom out", '$("graph").style.maxWidth = "100%";$(this).toggle();$(this).previous().toggle()', :style => 'display:none' %>
@@ -0,0 +1,17 @@
1
+ <h1>Authorization Rules</h1>
2
+ <p>Currently active rules in this application.</p>
3
+ <p><%= navigation %></p>
4
+ <style type="text/css">
5
+ pre .constant {color: #a00;}
6
+ pre .special {color: red;}
7
+ pre .operator {color: red;}
8
+ pre .statement {color: #00a;}
9
+ pre .proc {color: #0a0;}
10
+ pre .privilege, pre .context {font-weight: bold}
11
+ pre .preproc, pre .comment, pre .comment span {color: grey !important;}
12
+ pre .note {color: #a00; position:absolute; cursor: help; left: 15px }
13
+ pre.with-notes {padding-left: 35px}
14
+ </style>
15
+ <pre class="with-notes">
16
+ <%= policy_analysis_hints(syntax_highlight(h(@auth_rules_script)), @auth_rules_script) %>
17
+ </pre>
@@ -0,0 +1,36 @@
1
+ <h1>Authorization Usage</h1>
2
+ <%= render 'authorization_rules/show_graph' %>
3
+ <p>Filter rules in actions by controller:</p>
4
+ <p><%= navigation %></p>
5
+ <style type="text/css">
6
+ .auth-usages th { text-align: left; padding-top: 1em }
7
+ .auth-usages td { padding-right: 1em }
8
+ .auth-usages tr.action { cursor: pointer }
9
+ .auth-usages tr.unprotected { background: #FFA399 }
10
+ .auth-usages tr.no-attribute-check { background: #FFE599 }
11
+ /*.auth-usages tr.catch-all td.privilege,*/
12
+ .auth-usages tr.default-privilege td.privilege,
13
+ .auth-usages tr.default-context td.context { color: #888888 }
14
+ #graph-container {margin: 1em; border:1px solid #ccc; max-width:50%; position:fixed; right:0;}
15
+ </style>
16
+ <table class="auth-usages">
17
+ <% @auth_usages_by_controller.keys.sort {|c1, c2| c1.name <=> c2.name}.each do |controller| %>
18
+ <% default_context = controller.controller_name.to_sym rescue nil %>
19
+ <tr>
20
+ <th colspan="3"><%= h controller.controller_name %></th>
21
+ </tr>
22
+ <% @auth_usages_by_controller[controller].keys.sort {|c1, c2| c1.to_s <=> c2.to_s}.each do |action| %>
23
+ <% auth_info = @auth_usages_by_controller[controller][action] %>
24
+ <% first_permission = auth_info[:controller_permissions] && auth_info[:controller_permissions][0] %>
25
+ <tr class="action <%= auth_usage_info_classes(auth_info) %>" title="<%= auth_usage_info_title(auth_info) %>" onclick="show_graph('<%= auth_info[:privilege] || action %>','<%= auth_info[:context] || default_context %>')">
26
+ <td><%= h action %></td>
27
+ <% if first_permission %>
28
+ <td class="privilege"><%= h auth_info[:privilege] || action %></td>
29
+ <td class="context"><%= h auth_info[:context] || default_context %></td>
30
+ <% else %>
31
+ <td></td><td></td>
32
+ <% end %>
33
+ </tr>
34
+ <% end %>
35
+ <% end %>
36
+ </table>
@@ -0,0 +1,20 @@
1
+ authorization do
2
+ role :guest do
3
+ # add permissions for guests here, e.g.
4
+ #has_permission_on :conferences, :to => :read
5
+ end
6
+
7
+ # permissions on other roles, such as
8
+ #role :admin do
9
+ # has_permission_on :conferences, :to => :manage
10
+ #end
11
+ end
12
+
13
+ privileges do
14
+ # default privilege hierarchies to facilitate RESTful Rails apps
15
+ privilege :manage, :includes => [:create, :read, :update, :delete]
16
+ privilege :read, :includes => [:index, :show]
17
+ privilege :create, :includes => :new
18
+ privilege :update, :includes => :edit
19
+ privilege :delete, :includes => :destroy
20
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ if Authorization::activate_authorization_rules_browser?
3
+ map.resources :authorization_rules, :only => [:index],
4
+ :collection => {:graph => :get, :change => :get, :suggest_change => :get}
5
+ map.resources :authorization_usages, :only => :index
6
+ end
7
+ end