sinatra_resource 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.mdown +28 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/examples/datacatalog/Rakefile +23 -0
- data/examples/datacatalog/app.rb +15 -0
- data/examples/datacatalog/config/config.rb +66 -0
- data/examples/datacatalog/config/config.yml +11 -0
- data/examples/datacatalog/config.ru +6 -0
- data/examples/datacatalog/lib/base.rb +9 -0
- data/examples/datacatalog/lib/resource.rb +73 -0
- data/examples/datacatalog/lib/roles.rb +15 -0
- data/examples/datacatalog/models/categorization.rb +31 -0
- data/examples/datacatalog/models/category.rb +28 -0
- data/examples/datacatalog/models/source.rb +33 -0
- data/examples/datacatalog/models/user.rb +51 -0
- data/examples/datacatalog/resources/categories.rb +32 -0
- data/examples/datacatalog/resources/sources.rb +35 -0
- data/examples/datacatalog/resources/users.rb +26 -0
- data/examples/datacatalog/tasks/db.rake +29 -0
- data/examples/datacatalog/tasks/test.rake +16 -0
- data/examples/datacatalog/test/helpers/assertions/assert_include.rb +17 -0
- data/examples/datacatalog/test/helpers/assertions/assert_not_include.rb +17 -0
- data/examples/datacatalog/test/helpers/lib/model_factories.rb +49 -0
- data/examples/datacatalog/test/helpers/lib/model_helpers.rb +30 -0
- data/examples/datacatalog/test/helpers/lib/request_helpers.rb +53 -0
- data/examples/datacatalog/test/helpers/model_test_helper.rb +5 -0
- data/examples/datacatalog/test/helpers/resource_test_helper.rb +5 -0
- data/examples/datacatalog/test/helpers/shared/api_keys.rb +48 -0
- data/examples/datacatalog/test/helpers/shared/common_body_responses.rb +15 -0
- data/examples/datacatalog/test/helpers/shared/status_codes.rb +61 -0
- data/examples/datacatalog/test/helpers/test_cases/model_test_case.rb +6 -0
- data/examples/datacatalog/test/helpers/test_cases/resource_test_case.rb +36 -0
- data/examples/datacatalog/test/helpers/test_helper.rb +36 -0
- data/examples/datacatalog/test/models/categorization_test.rb +40 -0
- data/examples/datacatalog/test/models/category_test.rb +35 -0
- data/examples/datacatalog/test/models/source_test.rb +37 -0
- data/examples/datacatalog/test/models/user_test.rb +77 -0
- data/examples/datacatalog/test/resources/categories/categories_delete_test.rb +112 -0
- data/examples/datacatalog/test/resources/categories/categories_get_many_test.rb +58 -0
- data/examples/datacatalog/test/resources/categories/categories_get_one_test.rb +75 -0
- data/examples/datacatalog/test/resources/categories/categories_post_test.rb +135 -0
- data/examples/datacatalog/test/resources/categories/categories_put_test.rb +140 -0
- data/examples/datacatalog/test/resources/sources/sources_delete_test.rb +112 -0
- data/examples/datacatalog/test/resources/sources/sources_get_many_test.rb +58 -0
- data/examples/datacatalog/test/resources/sources/sources_get_one_test.rb +74 -0
- data/examples/datacatalog/test/resources/sources/sources_post_test.rb +184 -0
- data/examples/datacatalog/test/resources/sources/sources_put_test.rb +227 -0
- data/examples/datacatalog/test/resources/users/users_delete_test.rb +134 -0
- data/examples/datacatalog/test/resources/users/users_get_many_test.rb +111 -0
- data/examples/datacatalog/test/resources/users/users_get_one_test.rb +75 -0
- data/examples/datacatalog/test/resources/users/users_post_test.rb +142 -0
- data/examples/datacatalog/test/resources/users/users_put_test.rb +171 -0
- data/lib/builder/helpers.rb +319 -0
- data/lib/builder/mongo_helpers.rb +70 -0
- data/lib/builder.rb +84 -0
- data/lib/exceptions.rb +10 -0
- data/lib/resource.rb +171 -0
- data/lib/roles.rb +163 -0
- data/lib/sinatra_resource.rb +6 -0
- data/notes/keywords.mdown +1 -0
- data/notes/permissions.mdown +181 -0
- data/notes/questions.mdown +18 -0
- data/notes/see_also.mdown +3 -0
- data/notes/synonyms.mdown +7 -0
- data/notes/to_do.mdown +7 -0
- data/notes/uniform_interface.mdown +22 -0
- data/sinatra_resource.gemspec +183 -0
- data/spec/sinatra_resource_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- data/tasks/spec.rake +13 -0
- data/tasks/yard.rake +13 -0
- metadata +253 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../helpers/resource_test_helper')
|
2
|
+
|
3
|
+
class UsersGetOneResourceTest < ResourceTestCase
|
4
|
+
|
5
|
+
def app; DataCatalog::Users end
|
6
|
+
|
7
|
+
before do
|
8
|
+
@user = create_user
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
@user.destroy
|
13
|
+
end
|
14
|
+
|
15
|
+
context "get /:id" do
|
16
|
+
context "anonymous" do
|
17
|
+
before do
|
18
|
+
get "/#{@user.id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
use "return 401 because the API key is missing"
|
22
|
+
end
|
23
|
+
|
24
|
+
context "incorrect API key" do
|
25
|
+
before do
|
26
|
+
get "/#{@user.id}", :api_key => BAD_API_KEY
|
27
|
+
end
|
28
|
+
|
29
|
+
use "return 401 because the API key is invalid"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
%w(basic curator admin).each do |role|
|
34
|
+
context "#{role} : get /:fake_id" do
|
35
|
+
before do
|
36
|
+
get "/#{FAKE_ID}", :api_key => api_key_for(role)
|
37
|
+
end
|
38
|
+
|
39
|
+
use "return 404 Not Found"
|
40
|
+
use "return an empty response body"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
%w(basic curator).each do |role|
|
45
|
+
context "#{role} : get /:id" do
|
46
|
+
before do
|
47
|
+
get "/#{@user.id}", :api_key => api_key_for(role)
|
48
|
+
end
|
49
|
+
|
50
|
+
use "return 200 Ok"
|
51
|
+
doc_properties %w(name id created_at updated_at)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "owner : get /:id" do
|
56
|
+
before do
|
57
|
+
get "/#{@user.id}", :api_key => @user._api_key
|
58
|
+
end
|
59
|
+
|
60
|
+
use "return 200 Ok"
|
61
|
+
doc_properties %w(name email role _api_key id created_at updated_at)
|
62
|
+
end
|
63
|
+
|
64
|
+
%w(admin).each do |role|
|
65
|
+
context "#{role} : get /:id" do
|
66
|
+
before do
|
67
|
+
get "/#{@user.id}", :api_key => api_key_for(role)
|
68
|
+
end
|
69
|
+
|
70
|
+
use "return 200 Ok"
|
71
|
+
doc_properties %w(name email role _api_key id created_at updated_at)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../helpers/resource_test_helper')
|
2
|
+
|
3
|
+
class UsersPostResourceTest < ResourceTestCase
|
4
|
+
|
5
|
+
include DataCatalog
|
6
|
+
|
7
|
+
def app; Users end
|
8
|
+
|
9
|
+
before do
|
10
|
+
@user_count = User.all.length
|
11
|
+
@valid_params = {
|
12
|
+
:name => "New User",
|
13
|
+
:role => "basic"
|
14
|
+
}
|
15
|
+
@extra_admin_params = {
|
16
|
+
:role => "basic",
|
17
|
+
:_api_key => "222200004444"
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
shared "no new users" do
|
22
|
+
test "should not change number of user documents in database" do
|
23
|
+
assert_equal @user_count, User.all.length
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
shared "one new user" do
|
28
|
+
test "should add one user document to database" do
|
29
|
+
assert_equal @user_count + 1, User.all.length
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
shared "correct Location header" do
|
34
|
+
test "should set Location header correctly" do
|
35
|
+
base_uri = Config.environment_config["base_uri"]
|
36
|
+
path = %(/users/#{parsed_response_body["id"]})
|
37
|
+
expected = URI.join(base_uri, path).to_s
|
38
|
+
assert_equal expected, last_response.headers['Location']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "post /" do
|
43
|
+
context "anonymous" do
|
44
|
+
before do
|
45
|
+
post "/", @valid_params
|
46
|
+
end
|
47
|
+
|
48
|
+
use "return 401 because the API key is missing"
|
49
|
+
use "no new users"
|
50
|
+
end
|
51
|
+
|
52
|
+
context "incorrect API key" do
|
53
|
+
before do
|
54
|
+
post "/", @valid_params.merge(:api_key => BAD_API_KEY)
|
55
|
+
end
|
56
|
+
|
57
|
+
use "return 401 because the API key is invalid"
|
58
|
+
use "no new users"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
%w(basic curator).each do |role|
|
63
|
+
[:name, :role].each do |missing|
|
64
|
+
context "#{role} : post / but missing #{missing}" do
|
65
|
+
before do
|
66
|
+
post "/", valid_params_for(role).delete_if { |k, v| k == missing }
|
67
|
+
end
|
68
|
+
|
69
|
+
use "return 401 Unauthorized"
|
70
|
+
use "no new users"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
[:role, :_api_key, :id, :created_at, :updated_at].each do |invalid|
|
75
|
+
context "#{role} : post / but with #{invalid}" do
|
76
|
+
before do
|
77
|
+
post "/", valid_params_for(role).merge(invalid => 9)
|
78
|
+
end
|
79
|
+
|
80
|
+
use "return 401 Unauthorized"
|
81
|
+
use "no new users"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "#{role} : post / with valid params" do
|
86
|
+
before do
|
87
|
+
post "/", valid_params_for(role)
|
88
|
+
end
|
89
|
+
|
90
|
+
use "return 401 Unauthorized"
|
91
|
+
use "no new users"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
%w(admin).each do |role|
|
96
|
+
[:name, :role].each do |missing|
|
97
|
+
context "#{role} : post / but missing #{missing}" do
|
98
|
+
before do
|
99
|
+
post "/", valid_params_for(role).
|
100
|
+
merge(@extra_admin_params).delete_if { |k, v| k == missing }
|
101
|
+
end
|
102
|
+
|
103
|
+
use "return 400 Bad Request"
|
104
|
+
use "no new users"
|
105
|
+
missing_param missing
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
[:id, :created_at, :updated_at].each do |invalid|
|
110
|
+
context "#{role} : post / but with #{invalid}" do
|
111
|
+
before do
|
112
|
+
post "/", valid_params_for(role).
|
113
|
+
merge(@extra_admin_params).merge(invalid => 9)
|
114
|
+
end
|
115
|
+
|
116
|
+
use "return 400 Bad Request"
|
117
|
+
use "no new users"
|
118
|
+
invalid_param invalid
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "#{role} : post / with valid params" do
|
123
|
+
before do
|
124
|
+
post "/", valid_params_for(role).merge(@extra_admin_params)
|
125
|
+
end
|
126
|
+
|
127
|
+
use "return 201 Created"
|
128
|
+
use "correct Location header"
|
129
|
+
use "one new user"
|
130
|
+
doc_properties %w(name email role _api_key id created_at updated_at)
|
131
|
+
|
132
|
+
test "should set all fields in database" do
|
133
|
+
user = User.find_by_id(parsed_response_body["id"])
|
134
|
+
raise "Cannot find user" unless user
|
135
|
+
@valid_params.merge(@extra_admin_params).each_pair do |key, value|
|
136
|
+
assert_equal value, user[key]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../helpers/resource_test_helper')
|
2
|
+
|
3
|
+
class UsersPutResourceTest < ResourceTestCase
|
4
|
+
|
5
|
+
include DataCatalog
|
6
|
+
|
7
|
+
def app; Users end
|
8
|
+
|
9
|
+
before do
|
10
|
+
@user = create_user
|
11
|
+
@user_copy = @user.dup
|
12
|
+
@valid_params = {
|
13
|
+
:name => "Changed User",
|
14
|
+
:role => "curator"
|
15
|
+
}
|
16
|
+
@extra_admin_params = {
|
17
|
+
:_api_key => "222200004444"
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
@user.destroy
|
23
|
+
end
|
24
|
+
|
25
|
+
shared "user unchanged" do
|
26
|
+
test "should not change user in database" do
|
27
|
+
assert_equal @user_copy, User.find_by_id(@user.id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "put /:id" do
|
32
|
+
context "anonymous" do
|
33
|
+
before do
|
34
|
+
put "/#{@user.id}", @valid_params
|
35
|
+
end
|
36
|
+
|
37
|
+
use "return 401 because the API key is missing"
|
38
|
+
use "user unchanged"
|
39
|
+
end
|
40
|
+
|
41
|
+
context "incorrect API key" do
|
42
|
+
before do
|
43
|
+
put "/#{@user.id}", @valid_params.merge(:api_key => BAD_API_KEY)
|
44
|
+
end
|
45
|
+
|
46
|
+
use "return 401 because the API key is invalid"
|
47
|
+
use "user unchanged"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
%w(basic curator).each do |role|
|
52
|
+
[:created_at, :updated_at].each do |invalid|
|
53
|
+
context "#{role} : put / but with #{invalid}" do
|
54
|
+
before do
|
55
|
+
put "/#{@user.id}", valid_params_for(role).
|
56
|
+
merge(@extra_admin_params).merge(invalid => 9)
|
57
|
+
end
|
58
|
+
|
59
|
+
use "return 401 Unauthorized"
|
60
|
+
use "user unchanged"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
[:name, :role].each do |erase|
|
65
|
+
context "#{role} : put / but blanking out #{erase}" do
|
66
|
+
before do
|
67
|
+
put "/#{@user.id}", valid_params_for(role).
|
68
|
+
merge(@extra_admin_params).merge(erase => "")
|
69
|
+
end
|
70
|
+
|
71
|
+
use "return 401 Unauthorized"
|
72
|
+
use "user unchanged"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
[:name, :role].each do |missing|
|
77
|
+
context "#{role} : put /:id without #{missing}" do
|
78
|
+
before do
|
79
|
+
put "/#{@user.id}", valid_params_for(role).
|
80
|
+
merge(@extra_admin_params).delete_if { |k, v| k == missing }
|
81
|
+
end
|
82
|
+
|
83
|
+
use "return 401 Unauthorized"
|
84
|
+
use "user unchanged"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "#{role} : put /:id with valid params" do
|
89
|
+
before do
|
90
|
+
put "/#{@user.id}", valid_params_for(role).merge(@extra_admin_params)
|
91
|
+
end
|
92
|
+
|
93
|
+
use "return 401 Unauthorized"
|
94
|
+
use "user unchanged"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
%w(admin).each do |role|
|
99
|
+
[:created_at, :updated_at].each do |invalid|
|
100
|
+
context "#{role} : put / but with #{invalid}" do
|
101
|
+
before do
|
102
|
+
put "/#{@user.id}", valid_params_for(role).
|
103
|
+
merge(@extra_admin_params).merge(invalid => 9)
|
104
|
+
end
|
105
|
+
|
106
|
+
use "return 400 Bad Request"
|
107
|
+
use "user unchanged"
|
108
|
+
invalid_param invalid
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
[:name, :role].each do |erase|
|
113
|
+
context "#{role} : put / but blanking out #{erase}" do
|
114
|
+
before do
|
115
|
+
put "/#{@user.id}", valid_params_for(role).
|
116
|
+
merge(@extra_admin_params).merge(erase => "")
|
117
|
+
end
|
118
|
+
|
119
|
+
use "return 400 Bad Request"
|
120
|
+
use "user unchanged"
|
121
|
+
missing_param erase
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "#{role} : put /:id with no parameters" do
|
126
|
+
before do
|
127
|
+
put "/#{@user.id}", :api_key => api_key_for(role)
|
128
|
+
end
|
129
|
+
|
130
|
+
use "return 400 because no parameters were given"
|
131
|
+
use "user unchanged"
|
132
|
+
end
|
133
|
+
|
134
|
+
[:name, :role].each do |missing|
|
135
|
+
context "#{role} : put /:id without #{missing}" do
|
136
|
+
before do
|
137
|
+
put "/#{@user.id}", valid_params_for(role).
|
138
|
+
merge(@extra_admin_params).delete_if { |k, v| k == missing }
|
139
|
+
end
|
140
|
+
|
141
|
+
use "return 200 Ok"
|
142
|
+
doc_properties %w(name email role _api_key id created_at updated_at)
|
143
|
+
|
144
|
+
test "should change correct fields in database" do
|
145
|
+
user = User.find_by_id(@user.id)
|
146
|
+
@valid_params.merge(@extra_admin_params).each_pair do |key, value|
|
147
|
+
assert_equal(value, user[key]) if key != missing
|
148
|
+
end
|
149
|
+
assert_equal @user_copy[missing], user[missing]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "#{role} : put /:id with valid params" do
|
155
|
+
before do
|
156
|
+
put "/#{@user.id}", valid_params_for(role).merge(@extra_admin_params)
|
157
|
+
end
|
158
|
+
|
159
|
+
use "return 200 Ok"
|
160
|
+
doc_properties %w(name email role _api_key id created_at updated_at)
|
161
|
+
|
162
|
+
test "should change all fields in database" do
|
163
|
+
user = User.find_by_id(@user.id)
|
164
|
+
@valid_params.merge(@extra_admin_params).each_pair do |key, value|
|
165
|
+
assert_equal value, user[key]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
module SinatraResource
|
2
|
+
|
3
|
+
class Builder
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
# Build a resource, based on +document+, appropriate for +role+.
|
8
|
+
#
|
9
|
+
# @param [Symbol] role
|
10
|
+
# a role (such as :anonymous, :basic, or :admin)
|
11
|
+
#
|
12
|
+
# @param [MongoMapper::Document] document
|
13
|
+
#
|
14
|
+
# @return [Hash<String => Object>]
|
15
|
+
def build_resource(role, document)
|
16
|
+
resource = {}
|
17
|
+
config[:properties].each_pair do |property, hash|
|
18
|
+
if authorized?(:read, role, property)
|
19
|
+
resource[property.to_s] = value(property, document, hash)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
resource
|
23
|
+
end
|
24
|
+
|
25
|
+
# Builds a list of resources, based on +documents+, using the
|
26
|
+
# appropriate role for each document. (Delegates to +lookup_role+.)
|
27
|
+
#
|
28
|
+
# @param [Array<MongoMapper::Document>] documents
|
29
|
+
#
|
30
|
+
# @param [String] api_key
|
31
|
+
#
|
32
|
+
# @return [Array<Hash<String => Object>>]
|
33
|
+
def build_resources(documents)
|
34
|
+
documents.map do |document|
|
35
|
+
build_resource(lookup_role(document), document)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Halt unless the current params are ok for +action+ and +role+.
|
40
|
+
#
|
41
|
+
# @param [Symbol] action
|
42
|
+
# :read, :create, :update, or :delete
|
43
|
+
#
|
44
|
+
# @param [Symbol] role
|
45
|
+
# a role (such as :anonymous, :basic, or :admin)
|
46
|
+
#
|
47
|
+
# @return [undefined]
|
48
|
+
def check_params(action, role)
|
49
|
+
params_check_action(action)
|
50
|
+
params_check_action_and_role(action, role)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Halt unless the current role has permission to carry out +action+
|
54
|
+
#
|
55
|
+
# @param [Symbol] action
|
56
|
+
# :read, :create, :update, or :delete
|
57
|
+
#
|
58
|
+
# @param [Symbol] role
|
59
|
+
# a role (such as :anonymous, :basic, or :admin)
|
60
|
+
#
|
61
|
+
# @return [undefined]
|
62
|
+
def check_permission(action, role)
|
63
|
+
before_authorization(action, role)
|
64
|
+
unless authorized?(action, role)
|
65
|
+
error 401, convert(body_for(:unauthorized))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convert +object+ to desired format.
|
70
|
+
#
|
71
|
+
# For example, an application might want to convert +object+ to JSON or
|
72
|
+
# XML.
|
73
|
+
#
|
74
|
+
# Applications must override this method.
|
75
|
+
#
|
76
|
+
# @param [Object] object
|
77
|
+
#
|
78
|
+
# @return [String]
|
79
|
+
def convert
|
80
|
+
raise NotImplementedError
|
81
|
+
end
|
82
|
+
|
83
|
+
# Display +object+ as appropriate for +action+.
|
84
|
+
#
|
85
|
+
# @param [Object] object
|
86
|
+
#
|
87
|
+
# @return [String]
|
88
|
+
def display(action, object)
|
89
|
+
case action
|
90
|
+
when :read
|
91
|
+
when :create
|
92
|
+
response.status = 201
|
93
|
+
path = config[:path] + %(/#{object["id"]})
|
94
|
+
response.headers['Location'] = full_uri(path)
|
95
|
+
when :update
|
96
|
+
when :delete
|
97
|
+
response.status = 204
|
98
|
+
end
|
99
|
+
convert(object)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Convert a path to a full URI.
|
103
|
+
#
|
104
|
+
# Applications must override this method.
|
105
|
+
#
|
106
|
+
# @param [String] path
|
107
|
+
#
|
108
|
+
# @return [String]
|
109
|
+
def full_uri(path)
|
110
|
+
raise NotImplementedError
|
111
|
+
end
|
112
|
+
|
113
|
+
# Get role, using +id+ if specified. Delegates to +lookup_role+.
|
114
|
+
#
|
115
|
+
# When +id+ is present, it can help determine 'relative' roles such
|
116
|
+
# as 'ownership' of the current user of a particular document.
|
117
|
+
#
|
118
|
+
# @param [String, nil] id
|
119
|
+
#
|
120
|
+
# @return [Symbol]
|
121
|
+
def get_role(id=nil)
|
122
|
+
lookup_role(id ? config[:model].find_by_id(id) : nil)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return the minimum role required for +action+, and, if specified,
|
126
|
+
# +property+.
|
127
|
+
#
|
128
|
+
# @param [Symbol] action
|
129
|
+
# :read, :create, :update, or :delete
|
130
|
+
#
|
131
|
+
# @return [Symbol]
|
132
|
+
# a role (such as :anonymous, :basic, or :admin)
|
133
|
+
def minimum_role(action, property=nil)
|
134
|
+
if property.nil?
|
135
|
+
config[:permission][to_read_or_modify(action)]
|
136
|
+
else
|
137
|
+
config[:properties][property][to_r_or_w(action)]
|
138
|
+
end || :anonymous
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
# Is +role+ authorized for +action+, and, if specified, +property+?
|
144
|
+
#
|
145
|
+
# @param [Symbol] role
|
146
|
+
# a role (such as :anonymous, :basic, or :admin)
|
147
|
+
#
|
148
|
+
# @param [Symbol] action
|
149
|
+
# :read, :create, :update, or :delete
|
150
|
+
#
|
151
|
+
# @param [Symbol] property
|
152
|
+
# a property of a resource
|
153
|
+
#
|
154
|
+
# @return [Boolean]
|
155
|
+
def authorized?(action, role, property=nil)
|
156
|
+
klass = config[:roles]
|
157
|
+
klass.validate_role(role)
|
158
|
+
klass.satisfies?(role, minimum_role(action, property))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Application-level hook that runs as part of +check_permission+,
|
162
|
+
# before +authorized?(action, role)+ is called.
|
163
|
+
#
|
164
|
+
# For example, an application might want to throw custom errors
|
165
|
+
# in certain situations before +authorized?+ runs.
|
166
|
+
#
|
167
|
+
# Applications must override this method.
|
168
|
+
#
|
169
|
+
# @param [Symbol] action
|
170
|
+
# :read, :create, :update, or :delete
|
171
|
+
#
|
172
|
+
# @param [Symbol] role
|
173
|
+
# a role (such as :anonymous, :basic, or :admin)
|
174
|
+
#
|
175
|
+
# @return [String]
|
176
|
+
def before_authorization(action, role)
|
177
|
+
raise NotImplementedError
|
178
|
+
end
|
179
|
+
|
180
|
+
# Default body message for a +situation+
|
181
|
+
#
|
182
|
+
# @param [Symbol] situation
|
183
|
+
#
|
184
|
+
# @param [Object] object
|
185
|
+
#
|
186
|
+
# @return [String]
|
187
|
+
def body_for(situation, object=nil)
|
188
|
+
case situation
|
189
|
+
when :errors
|
190
|
+
{ "errors" => object }
|
191
|
+
when :internal_server_error
|
192
|
+
""
|
193
|
+
when :invalid_document
|
194
|
+
{ "errors" => object.errors.errors }
|
195
|
+
when :invalid_params
|
196
|
+
{ "errors" => { "invalid_params" => object } }
|
197
|
+
when :no_params
|
198
|
+
{ "errors" => "no_params" }
|
199
|
+
when :non_empty_params
|
200
|
+
{ "errors" => "non_empty_params" }
|
201
|
+
when :not_found
|
202
|
+
""
|
203
|
+
when :unauthorized
|
204
|
+
""
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Lookup the rol, using +document+ if specified.
|
209
|
+
#
|
210
|
+
# Applications must override this method.
|
211
|
+
#
|
212
|
+
# @param [MongoMapper::Document, nil] document
|
213
|
+
#
|
214
|
+
# @return [Symbol]
|
215
|
+
def lookup_role(document=nil)
|
216
|
+
raise NotImplementedError
|
217
|
+
end
|
218
|
+
|
219
|
+
# Are the params suitable for +action+? Raise 400 Bad Request if not.
|
220
|
+
#
|
221
|
+
# @param [Symbol] action
|
222
|
+
# :read, :create, :update, or :delete
|
223
|
+
#
|
224
|
+
# @return [undefined]
|
225
|
+
def params_check_action(action)
|
226
|
+
case action
|
227
|
+
when :read
|
228
|
+
unless params.empty?
|
229
|
+
error 400, convert(body_for(:non_empty_params))
|
230
|
+
end
|
231
|
+
when :create
|
232
|
+
# No need to complain. If there are problems,
|
233
|
+
# params_check_action_and_role will catch them.
|
234
|
+
when :update
|
235
|
+
if params.empty?
|
236
|
+
error 400, convert(body_for(:no_params))
|
237
|
+
end
|
238
|
+
when :delete
|
239
|
+
unless params.empty?
|
240
|
+
error 400, convert(body_for(:non_empty_params))
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Checks each parameter to make sure it is authorized for +action+ and
|
246
|
+
# +role+. Raises a 400 Bad Request if not authorized.
|
247
|
+
#
|
248
|
+
# @param [Symbol] action
|
249
|
+
# :read, :create, :update, or :delete
|
250
|
+
#
|
251
|
+
# @param [Symbol] role
|
252
|
+
# a role (such as :anonymous, :basic, or :admin)
|
253
|
+
#
|
254
|
+
# @return [undefined]
|
255
|
+
def params_check_action_and_role(action, role)
|
256
|
+
invalid = []
|
257
|
+
params.each_pair do |property, value|
|
258
|
+
invalid << property if !authorized?(action, role, property.intern)
|
259
|
+
end
|
260
|
+
unless invalid.empty?
|
261
|
+
error 400, convert(body_for(:invalid_params, invalid))
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Converts +action+ to :r or :w (i.e. read or write).
|
266
|
+
#
|
267
|
+
# @param [Symbol] action
|
268
|
+
# :read, :create, or :update
|
269
|
+
#
|
270
|
+
# @return [Symbol]
|
271
|
+
# :r or :w
|
272
|
+
def to_r_or_w(action)
|
273
|
+
case action
|
274
|
+
when :read then :r
|
275
|
+
when :create then :w
|
276
|
+
when :update then :w
|
277
|
+
else raise "Unexpected action : #{action.inspect}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Converts +action+ to (:read or :modify).
|
282
|
+
#
|
283
|
+
# @param [Symbol] action
|
284
|
+
# :read, :create, :update, or :delete
|
285
|
+
#
|
286
|
+
# @return [Symbol]
|
287
|
+
# :read or :modify
|
288
|
+
def to_read_or_modify(action)
|
289
|
+
case action
|
290
|
+
when :read then :read
|
291
|
+
when :create then :modify
|
292
|
+
when :update then :modify
|
293
|
+
when :delete then :modify
|
294
|
+
else raise "Unexpected action : #{action.inspect}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Lookup +attribute+ in +document+
|
299
|
+
#
|
300
|
+
# @param [Symbol] attribute
|
301
|
+
# an attribute of +document+
|
302
|
+
#
|
303
|
+
# @param [MongoMapper::Document] document
|
304
|
+
#
|
305
|
+
# @return [undefined]
|
306
|
+
def value(attribute, document, property_hash)
|
307
|
+
proc = property_hash[:read_proc]
|
308
|
+
if proc
|
309
|
+
proc.call(document)
|
310
|
+
else
|
311
|
+
document[attribute == :id ? :_id : attribute]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|