ioquatix-account_engine 0.1.0

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.
Files changed (74) hide show
  1. data/README +0 -0
  2. data/lib/account_engine/configuration.rb +101 -0
  3. data/lib/account_engine/controller.rb +246 -0
  4. data/lib/account_engine/helper.rb +104 -0
  5. data/lib/account_engine/password.rb +432 -0
  6. data/lib/account_engine/support.rb +12 -0
  7. data/lib/account_engine/user_account/class_methods.rb +63 -0
  8. data/lib/account_engine/user_account.rb +184 -0
  9. data/lib/account_engine.rb +63 -0
  10. data/rails/app/controllers/account_controller.rb +162 -0
  11. data/rails/app/controllers/permissions_controller.rb +90 -0
  12. data/rails/app/controllers/roles_controller.rb +133 -0
  13. data/rails/app/controllers/users_controller.rb +144 -0
  14. data/rails/app/helpers/account_helper.rb +3 -0
  15. data/rails/app/helpers/permissions_helper.rb +3 -0
  16. data/rails/app/helpers/roles_helper.rb +3 -0
  17. data/rails/app/helpers/users_helper.rb +3 -0
  18. data/rails/app/models/permission.rb +129 -0
  19. data/rails/app/models/role.rb +60 -0
  20. data/rails/app/models/user.rb +5 -0
  21. data/rails/app/models/user_notify.rb +75 -0
  22. data/rails/app/views/account/_form.rhtml +8 -0
  23. data/rails/app/views/account/change_password.rhtml +17 -0
  24. data/rails/app/views/account/edit.rhtml +5 -0
  25. data/rails/app/views/account/forgot_password.rhtml +12 -0
  26. data/rails/app/views/account/home.rhtml +3 -0
  27. data/rails/app/views/account/login.rhtml +27 -0
  28. data/rails/app/views/account/logout.rhtml +8 -0
  29. data/rails/app/views/account/signup.rhtml +28 -0
  30. data/rails/app/views/permissions/_form.rhtml +14 -0
  31. data/rails/app/views/permissions/_list.rhtml +38 -0
  32. data/rails/app/views/permissions/edit.rhtml +5 -0
  33. data/rails/app/views/permissions/index.rhtml +3 -0
  34. data/rails/app/views/permissions/new.rhtml +5 -0
  35. data/rails/app/views/roles/_form.rhtml +8 -0
  36. data/rails/app/views/roles/_permissions.rhtml +25 -0
  37. data/rails/app/views/roles/edit.rhtml +5 -0
  38. data/rails/app/views/roles/index.rhtml +34 -0
  39. data/rails/app/views/roles/new.rhtml +5 -0
  40. data/rails/app/views/roles/show.rhtml +20 -0
  41. data/rails/app/views/user_notify/change_password.rhtml +10 -0
  42. data/rails/app/views/user_notify/delete.rhtml +5 -0
  43. data/rails/app/views/user_notify/forgot_password.rhtml +11 -0
  44. data/rails/app/views/user_notify/pending_delete.rhtml +9 -0
  45. data/rails/app/views/user_notify/signup.rhtml +12 -0
  46. data/rails/app/views/users/_form.rhtml +12 -0
  47. data/rails/app/views/users/edit.rhtml +5 -0
  48. data/rails/app/views/users/index.rhtml +38 -0
  49. data/rails/app/views/users/new.rhtml +5 -0
  50. data/rails/app/views/users/roles.rhtml +42 -0
  51. data/rails/app/views/users/show.rhtml +36 -0
  52. data/rails/assets/images/default/omnipotent.png +0 -0
  53. data/rails/assets/images/default/system.png +0 -0
  54. data/rails/assets/images/permissions/create.png +0 -0
  55. data/rails/assets/images/permissions/sync.png +0 -0
  56. data/rails/assets/images/roles/add_permission.png +0 -0
  57. data/rails/assets/images/roles/create.png +0 -0
  58. data/rails/assets/images/roles/edit.png +0 -0
  59. data/rails/assets/images/roles/remove_permission.png +0 -0
  60. data/rails/assets/images/roles/user.png +0 -0
  61. data/rails/assets/images/table_background.png +0 -0
  62. data/rails/assets/images/users/create.png +0 -0
  63. data/rails/assets/images/users/destroy.png +0 -0
  64. data/rails/assets/images/users/edit.png +0 -0
  65. data/rails/assets/images/users/show.png +0 -0
  66. data/rails/assets/javascripts/account_engine.js +166 -0
  67. data/rails/assets/stylesheets/account_engine.css +7 -0
  68. data/rails/assets/stylesheets/check_password.css +10 -0
  69. data/rails/assets/stylesheets/simple.css +168 -0
  70. data/rails/db/migrate/001_initial_schema.rb +49 -0
  71. data/rails/init.rb +21 -0
  72. data/rails/routes.rb +5 -0
  73. data/rails/tasks/account_engine.rake +123 -0
  74. metadata +165 -0
@@ -0,0 +1,38 @@
1
+ <h1>Listing Users</h1>
2
+
3
+ <table id="user_list" class="listing">
4
+ <thead>
5
+ <tr>
6
+ <th>Login</th>
7
+ <th> </th>
8
+ <th>Email</th>
9
+ <th>Created</th>
10
+ <th>Logged In</th>
11
+ <th>Roles</th>
12
+ <th> </th>
13
+ </tr>
14
+ </thead>
15
+ <tfoot>
16
+ <tr>
17
+ <td colspan="7">
18
+ <%= link_if_authorized icon_tag(:create) + ' New User', new_user_path %>
19
+ </td>
20
+ </tr>
21
+ </tfoot>
22
+ <tbody>
23
+ <% for user in @users %>
24
+ <tr>
25
+ <td><%= link_if_authorized user.fullname, :action => 'show', :id => user.id %></td>
26
+ <td><%= link_if_authorized icon_tag(:edit), {:action => 'edit', :id => user} %></td>
27
+ <td><%= mail_to user.email, user.email %></td>
28
+ <td><%= user.created_at.strftime("%H:%I %Z, %d %b %Y") if user.created_at %></td>
29
+ <td><%= (user.logged_in_at.strftime("%H:%I %Z, %d %b %Y") if user.logged_in_at) || '-' %></td>
30
+ <td><%= user.roles.collect { |r| link_if_authorized r.name, :controller => 'roles', :action => 'show', :id => r.id }.join(", ") %></td>
31
+ <td><%= link_if_authorized icon_tag('edit', :controller => 'roles'), :action => 'roles', :id => user.id %></td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>
36
+
37
+ <%= will_paginate @users %>
38
+
@@ -0,0 +1,5 @@
1
+ <h1>New User</h1>
2
+
3
+ <% table_form_for :user, :url => {:action => :create} do |f| %>
4
+ <%= f.render_form %>
5
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <h1>Roles for User: <%= @user.fullname %></h1>
2
+
3
+ <table class="listing">
4
+ <thead>
5
+ <tr>
6
+ <th>Role</th>
7
+ <th>Granted</th>
8
+ <th>Revoke</th>
9
+ <th>Description</th>
10
+ </tr>
11
+ </thead>
12
+ <tfoot>
13
+ <tr>
14
+ <td colspan="4">
15
+ <%= link_to icon_tag('create', :controller => 'roles') + " New Role", :controller => 'roles', :action => 'new' %>
16
+ </td>
17
+ </tr>
18
+ </tfoot>
19
+ <% @roles.each do |r| %>
20
+ <tr>
21
+ <td><%= link_if_authorized r.name, :controller => 'roles', :action => 'show', :id => r.id %></td>
22
+ <% if @user.roles.include? r %>
23
+ <td>
24
+ <b>Granted</b>
25
+ </td>
26
+ <td>
27
+ <%= link_to "Revoke", {:action => 'remove_role', :id => @user.id, :role => r.id}, :post => true %>
28
+ </td>
29
+ <% else %>
30
+ <td>
31
+ <%= link_to "Grant", {:action => 'add_role', :id => @user.id, :role => r.id}, :post => true %>
32
+ </td>
33
+ <td>
34
+ </td>
35
+ <% end %>
36
+ <td>
37
+ <%= r.description %>
38
+ </td>
39
+ </tr>
40
+ <% end %>
41
+ </table>
42
+
@@ -0,0 +1,36 @@
1
+ <h1>User <%= @user.login %></h1>
2
+
3
+ <h2>Account Details</h2>
4
+
5
+ <dl>
6
+ <dt>User ID</dt>
7
+ <dd><%= @user.id %></dd>
8
+ <dt>Login</dt>
9
+ <dd><%= @user.login %></dd>
10
+ <dt>Email</dt>
11
+ <dd><%= @user.email %></dd>
12
+ </dl>
13
+
14
+ <dl>
15
+ <dt>Account created</dt>
16
+ <dd><%= @user.created_at %></dd>
17
+ <dt>Updated at</dt>
18
+ <dd><%= @user.updated_at %></dd>
19
+ <dt>Last logged in at</dt>
20
+ <dd><%= @user.logged_in_at %></dd>
21
+ </dl>
22
+
23
+ <h2>Account Status</h2>
24
+
25
+ <dl>
26
+ <dt>Verified?</dt>
27
+ <dd><%= @user.verified? ? "Yes" : "No" %></dd>
28
+ <dt>Deleted?</dt>
29
+ <dd><%= @user.deleted? ? "Yes" : "No" %></dd>
30
+ <% if @user.security_token != nil %>
31
+ <dt>Security Token</dt>
32
+ <dd><%= @user.security_token %></dd>
33
+ <dt>Token Expiry</dt>
34
+ <dd><%= @user.token_expiry %></dd>
35
+ <% end %>
36
+ </dl>
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,166 @@
1
+
2
+ function updatePasswordStatus (okay) {
3
+ var passwordChangeButton = $("passwordChangeButton");
4
+ var errorImage = $("passwordErrorStatus");
5
+
6
+ if (okay) {
7
+ if (errorImage) {
8
+ errorImage.removeClassName("okay");
9
+ errorImage.addClassName("error");
10
+ }
11
+
12
+ passwordChangeButton.disabled = 1;
13
+ } else {
14
+ if (errorImage) {
15
+ errorImage.removeClassName("error");
16
+ errorImage.addClassName("okay");
17
+ }
18
+
19
+ passwordChangeButton.disabled = null;
20
+ }
21
+ }
22
+
23
+ function checkPasswords () {
24
+ var password = $F("password");
25
+ var passwordCopy = $F("passwordCopy");
26
+
27
+ if (password == "") {
28
+ updatePasswordStatus(false);
29
+ }
30
+
31
+ updatePasswordStatus(password != passwordCopy)
32
+ }
33
+
34
+ // http://www.movable-type.co.uk/scripts/sha1.html
35
+ function sha1Hash(msg)
36
+ {
37
+ // constants [§4.2.1]
38
+ var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
39
+
40
+
41
+ // PREPROCESSING
42
+
43
+ msg += String.fromCharCode(0x80); // add trailing '1' bit to string [§5.1.1]
44
+
45
+ // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
46
+ var l = Math.ceil(msg.length/4) + 2; // long enough to contain msg plus 2-word length
47
+ var N = Math.ceil(l/16); // in N 16-int blocks
48
+ var M = new Array(N);
49
+ for (var i=0; i<N; i++) {
50
+ M[i] = new Array(16);
51
+ for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
52
+ M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
53
+ (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
54
+ }
55
+ }
56
+ // add length (in bits) into final pair of 32-bit integers (big-endian) [5.1.1]
57
+ // note: most significant word would be ((len-1)*8 >>> 32, but since JS converts
58
+ // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
59
+ M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
60
+ M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
61
+
62
+ // set initial hash value [§5.3.1]
63
+ var H0 = 0x67452301;
64
+ var H1 = 0xefcdab89;
65
+ var H2 = 0x98badcfe;
66
+ var H3 = 0x10325476;
67
+ var H4 = 0xc3d2e1f0;
68
+
69
+ // HASH COMPUTATION [§6.1.2]
70
+
71
+ var W = new Array(80); var a, b, c, d, e;
72
+ for (var i=0; i<N; i++) {
73
+
74
+ // 1 - prepare message schedule 'W'
75
+ for (var t=0; t<16; t++) W[t] = M[i][t];
76
+ for (var t=16; t<80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
77
+
78
+ // 2 - initialise five working variables a, b, c, d, e with previous hash value
79
+ a = H0; b = H1; c = H2; d = H3; e = H4;
80
+
81
+ // 3 - main loop
82
+ for (var t=0; t<80; t++) {
83
+ var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
84
+ var T = (ROTL(a,5) + f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
85
+ e = d;
86
+ d = c;
87
+ c = ROTL(b, 30);
88
+ b = a;
89
+ a = T;
90
+ }
91
+
92
+ // 4 - compute the new intermediate hash value
93
+ H0 = (H0+a) & 0xffffffff; // note 'addition modulo 2^32'
94
+ H1 = (H1+b) & 0xffffffff;
95
+ H2 = (H2+c) & 0xffffffff;
96
+ H3 = (H3+d) & 0xffffffff;
97
+ H4 = (H4+e) & 0xffffffff;
98
+ }
99
+
100
+ return H0.toHexStr() + H1.toHexStr() + H2.toHexStr() + H3.toHexStr() + H4.toHexStr();
101
+ }
102
+
103
+ //
104
+ // function 'f' [§4.1.1]
105
+ //
106
+ function f(s, x, y, z)
107
+ {
108
+ switch (s) {
109
+ case 0: return (x & y) ^ (~x & z); // Ch()
110
+ case 1: return x ^ y ^ z; // Parity()
111
+ case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
112
+ case 3: return x ^ y ^ z; // Parity()
113
+ }
114
+ }
115
+
116
+ //
117
+ // rotate left (circular left shift) value x by n positions [§3.2.5]
118
+ //
119
+ function ROTL(x, n)
120
+ {
121
+ return (x<<n) | (x>>>(32-n));
122
+ }
123
+
124
+ //
125
+ // extend Number class with a tailored hex-string method
126
+ // (note toString(16) is implementation-dependant, and
127
+ // in IE returns signed numbers when used on full words)
128
+ //
129
+ Number.prototype.toHexStr = function()
130
+ {
131
+ var s="", v;
132
+ for (var i=7; i>=0; i--) { v = (this>>>(i*4)) & 0xf; s += v.toString(16); }
133
+ return s;
134
+ }
135
+
136
+ def self.hashed(str)
137
+ # check if a salt has been set...
138
+ if AccountEngine.salt == nil
139
+ raise "You must define a :salt value in the configuration for the AccountEngine module."
140
+ end
141
+
142
+ return Digest::SHA1.hexdigest("#{AccountEngine.salt}-#{str}}")[0..39]
143
+ end
144
+
145
+ var siteSalt = "...";
146
+ var userSalt = "...";
147
+ var challenge = "...";
148
+
149
+ function hashed(s) {
150
+ return sha1Hash(siteSalt + s)
151
+ }
152
+
153
+ // Challenge-response JS..
154
+ function computeResponse(password, challenge) {
155
+ hashedPassword = hashed(userSalt + password);
156
+
157
+ hashedChallenge = hashed(challenge + hashedPassword);
158
+
159
+ return hashedChallenge;
160
+ }
161
+
162
+ function processForm() {
163
+ currentPassword = $F("password");
164
+
165
+ $F("password") = computeResponse(currentPassword);
166
+ }
@@ -0,0 +1,7 @@
1
+ .granted {
2
+ background-color: #cfc;
3
+ }
4
+
5
+ .unassigned {
6
+ background-color: #fcc;
7
+ }
@@ -0,0 +1,10 @@
1
+ #passwordErrorStatus {
2
+ }
3
+
4
+ #passwordErrorStatus.okay {
5
+ border: 1px dashed green;
6
+ }
7
+
8
+ #passwordErrorStatus.error {
9
+ border: 1px dashed red;
10
+ }
@@ -0,0 +1,168 @@
1
+ .granted {
2
+ background-color: #cfc;
3
+ }
4
+
5
+ .unassigned {
6
+ background-color: #fcc;
7
+ }
8
+
9
+ h1 {
10
+ border-bottom: 3px solid #bbb;
11
+ text-shadow: 3px 1px #ccc;
12
+ }
13
+
14
+ form table {
15
+ /*border-collapse: collapse;*/
16
+ width: 80%;
17
+ margin: auto;
18
+
19
+ font-size: 80%;
20
+ background-color: #eef;
21
+
22
+ padding: 10px;
23
+
24
+ border: 1px solid #779;
25
+ }
26
+
27
+ form table tr > :first-child {
28
+ text-align: right;
29
+ width: 30%;
30
+ }
31
+
32
+ form table tr.form_errors td {
33
+ text-align: left;
34
+ }
35
+
36
+ form table td[colspan="2"] {
37
+ border-top: 1px solid #666;
38
+ background-color: #fafaff;
39
+ padding: 5px;
40
+ }
41
+
42
+ textarea, input, table.detail_select {
43
+ padding: 2px;
44
+ margin: 2px;
45
+ }
46
+
47
+ textarea {
48
+ width: 80%;
49
+ height: 60px;
50
+ }
51
+
52
+ table.listing {
53
+ font-size: 75%;
54
+
55
+ border-collapse: collapse;
56
+ width: 100%;
57
+
58
+ border: 1px solid black;
59
+ }
60
+
61
+ table.listing td {
62
+ padding: 5px;
63
+ }
64
+
65
+ table.listing tbody .description {
66
+ width: 25%;
67
+ font-size: 90%;
68
+ }
69
+
70
+ table.listing thead {
71
+ font-weight: bold;
72
+ background: url(/plugin_assets/account_engine/images/table_background.png);
73
+ }
74
+
75
+ table.listing .when {
76
+ width: 80px;
77
+ text-align: center;
78
+ }
79
+
80
+ table.listing .total, table.listing .subtotal, table.listing .unitprice, table.listing .quantity, table.listing .tax {
81
+ text-align: right;
82
+ width: 9%;
83
+ border-left: 1px dashed #ccc;
84
+ }
85
+
86
+ table.listing tfoot {
87
+ background: url(/plugin_assets/account_engine/images/table_background.png);
88
+ }
89
+
90
+ table.listing tfoot td {
91
+ text-align: right;
92
+ font-weight: bold;
93
+ }
94
+
95
+ table.listing tr .s {
96
+ cbackground-color: #aac;
97
+ ccolor: white;
98
+ }
99
+
100
+ table.detail_select {
101
+ background-color: white;
102
+ }
103
+
104
+ .icon {
105
+ vertical-align: -20%;
106
+ }
107
+
108
+ img {
109
+ border: none;
110
+ }
111
+
112
+ a {
113
+ color: #00f;
114
+ text-decoration: none;
115
+ }
116
+
117
+ a:hover {
118
+ color: #99f;
119
+ }
120
+
121
+ #menu {
122
+ padding: 2px;
123
+ border: 1px dashed #333;
124
+ background-color: #abc;
125
+ padding-left: 10px;
126
+ }
127
+
128
+ #menu a {
129
+ font-size: 15px;
130
+ margin-right: 5px;
131
+ padding: 2px 6px 2px 6px;
132
+
133
+ color: #22f;
134
+ text-decoration: none;
135
+
136
+ text-shadow: 2px 2px 2px #666;
137
+ }
138
+
139
+ #menu a:hover {
140
+ border-top: 4px solid black;
141
+ border-bottom: 4px solid black;
142
+ }
143
+
144
+ #menu a:active {
145
+ background-color: #cde;
146
+ }
147
+
148
+ #messages {
149
+ margin: 10px;
150
+ }
151
+
152
+ h2 {
153
+ font-size: 14px;
154
+ padding: 2px;
155
+ background-color: #ececec;
156
+ }
157
+
158
+ .property_list {
159
+ border: 1px dashed #ccf;
160
+ padding: 2px;
161
+ margin: 10px;
162
+ }
163
+
164
+ .property_list li {
165
+ display: inline-block;
166
+ padding: 2px;
167
+ background-color: #efefef;
168
+ }
@@ -0,0 +1,49 @@
1
+ class InitialSchema < ActiveRecord::Migration
2
+ def self.up
3
+ create_table AccountEngine.users_table do |t|
4
+ t.column "login", :string, :limit => 80, :default => "", :null => false
5
+ t.column "salted_password", :string, :limit => 40, :default => ""
6
+ t.column "salt", :string, :limit => 40, :default => ""
7
+ t.column "verified", :integer, :default => 0
8
+ t.column "security_token", :string, :limit => 40
9
+ t.column "token_expiry", :datetime
10
+ t.column "created_at", :datetime
11
+ t.column "updated_at", :datetime
12
+ t.column "logged_in_at", :datetime
13
+ t.column "deleted", :integer, :default => 0
14
+ t.column "email", :string, :limit => 60
15
+ end
16
+
17
+ create_table AccountEngine.permissions_table, :force => true do |t|
18
+ t.column "controller", :string, :default => "", :null => false
19
+ t.column "action", :string, :default => "", :null => false
20
+ t.column "system", :boolean, :default => false, :null => false
21
+ t.column "description", :string
22
+ end
23
+
24
+ create_table AccountEngine.permissions_roles_table, :id => false, :force => true do |t|
25
+ t.column "permission_id", :integer, :default => 0, :null => false
26
+ t.column "role_id", :integer, :default => 0, :null => false
27
+ end
28
+
29
+ create_table AccountEngine.users_roles_table, :id => false, :force => true do |t|
30
+ t.column "user_id", :integer, :default => 0, :null => false
31
+ t.column "role_id", :integer, :default => 0, :null => false
32
+ end
33
+
34
+ create_table AccountEngine.roles_table, :force => true do |t|
35
+ t.column "name", :string, :default => "", :null => false
36
+ t.column "description", :string
37
+ t.column "system", :boolean, :default => false, :null => false
38
+ t.column "omnipotent", :boolean, :default => false, :null => false
39
+ end
40
+ end
41
+
42
+ def self.down
43
+ drop_table AccountEngine.permissions_table
44
+ drop_table AccountEngine.permissions_roles_table
45
+ drop_table AccountEngine.users_roles_table
46
+ drop_table AccountEngine.roles_table
47
+ drop_table AccountEngine.users_table
48
+ end
49
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2006 Sammi Williams <sammi@oriontransfer.co.nz>
2
+ #
3
+ # The GNU General Public License (GPL)
4
+ # Version 2, June 1991
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+
20
+ # load up all the required files we need...
21
+ require 'account_engine'
data/rails/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+
2
+ resource :account, :controller => "account", :collection => { :login => :get, :logout => :get, :home => :get }
3
+ resources :permissions, :collection => { :resync => :get }
4
+ resources :roles, :member => { :add_permission => :post, :remove_permission => :post }
5
+ resources :users