scrivito_sdk 1.9.1 → 1.10.0.rc1
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.
- checksums.yaml +4 -4
- data/app/controllers/scrivito/obj_class_controller.rb +12 -3
- data/app/controllers/scrivito/users_controller.rb +11 -0
- data/app/helpers/scrivito_helper.rb +4 -4
- data/config/ca-bundle.crt +1 -1
- data/lib/assets/javascripts/scrivito.js +12 -12
- data/lib/assets/javascripts/scrivito_ui_redirect.js +2 -2
- data/lib/assets/javascripts/scrivito_with_js_sdk.js +20123 -19370
- data/lib/assets/stylesheets/scrivito.css +1 -1
- data/lib/scrivito/attribute_content.rb +25 -15
- data/lib/scrivito/attribute_serializer.rb +3 -1
- data/lib/scrivito/backend/obj_load.rb +1 -5
- data/lib/scrivito/backend/obj_query.rb +2 -6
- data/lib/scrivito/backend/parent_path_index.rb +4 -7
- data/lib/scrivito/backend/path_index.rb +5 -8
- data/lib/scrivito/backend/permalink_index.rb +4 -7
- data/lib/scrivito/basic_obj.rb +70 -67
- data/lib/scrivito/basic_widget.rb +1 -0
- data/lib/scrivito/binary.rb +17 -2
- data/lib/scrivito/binary_param_verifier.rb +30 -34
- data/lib/scrivito/client_error.rb +2 -0
- data/lib/scrivito/cms_data_cache.rb +71 -73
- data/lib/scrivito/cms_rest_api.rb +114 -118
- data/lib/scrivito/cms_rest_api/rate_limit.rb +21 -26
- data/lib/scrivito/cms_routing.rb +1 -1
- data/lib/scrivito/configuration.rb +412 -373
- data/lib/scrivito/connection_manager.rb +42 -36
- data/lib/scrivito/deprecation.rb +11 -15
- data/lib/scrivito/diff.rb +6 -8
- data/lib/scrivito/gem_info.rb +9 -6
- data/lib/scrivito/generator_helper.rb +5 -7
- data/lib/scrivito/migrations/cms_backend.rb +11 -8
- data/lib/scrivito/migrations/migrator.rb +19 -21
- data/lib/scrivito/obj_search_enumerator.rb +24 -24
- data/lib/scrivito/request_homepage.rb +10 -14
- data/lib/scrivito/route.rb +27 -31
- data/lib/scrivito/sdk_engine.rb +8 -4
- data/lib/scrivito/tag_renderer.rb +17 -23
- data/lib/scrivito/test_request.rb +7 -9
- data/lib/scrivito/ui_config.rb +0 -19
- data/lib/scrivito/user.rb +89 -99
- data/lib/scrivito/workspace.rb +12 -16
- data/lib/scrivito/workspace_data.rb +2 -6
- data/lib/scrivito_sdk.rb +0 -13
- data/lib/tasks/cache.rake +2 -0
- metadata +18 -4
@@ -3,45 +3,41 @@ module Scrivito
|
|
3
3
|
module BinaryParamVerifier
|
4
4
|
InvalidSignature = Class.new(StandardError)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
6
|
+
def self.verify(encrypted_params)
|
7
|
+
params = decrypt(encrypted_params)
|
8
|
+
expires = DateConversion.deserialize_from_backend(params['expires'])
|
9
|
+
raise InvalidSignature if expires && expires < Time.zone.now
|
10
|
+
verify_without_expire(encrypted_params)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
def self.verify_without_expire(encrypted_params)
|
14
|
+
params = decrypt(encrypted_params)
|
15
|
+
Binary.new(params['binary_id'], params['expires'].nil?,
|
16
|
+
transformation_definition: params['transformation_definition'],
|
17
|
+
obj_id: params['obj_id'])
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
message_verifier.generate(params)
|
20
|
+
def self.generate(binary)
|
21
|
+
params = {
|
22
|
+
binary_id: binary.id,
|
23
|
+
obj_id: binary.obj_id,
|
24
|
+
transformation_definition: binary.transformation_definition,
|
25
|
+
}
|
26
|
+
if binary.private?
|
27
|
+
params[:expires] = DateConversion.serialize_for_backend(Time.zone.now + 1.hour)
|
31
28
|
end
|
29
|
+
message_verifier.generate(params)
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
raise InvalidSignature
|
39
|
-
end
|
32
|
+
private_class_method def self.decrypt(encrypted_params)
|
33
|
+
message_verifier.verify(encrypted_params)
|
34
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
35
|
+
raise InvalidSignature
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
38
|
+
private_class_method def self.message_verifier
|
39
|
+
ActiveSupport::MessageVerifier.new(
|
40
|
+
Rails.application.secrets.secret_key_base, serializer: JSON)
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
@@ -4,87 +4,85 @@ module Scrivito
|
|
4
4
|
module CmsDataCache
|
5
5
|
VERSION = 'v3'.freeze
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def cache_path=(path)
|
14
|
-
clear_cache_chain
|
15
|
-
@cache_path = path
|
16
|
-
end
|
17
|
-
|
18
|
-
def first_level_cache
|
19
|
-
@first_level_cache ||= Cache::FileStore.new(path: @cache_path, next_store: second_level_cache)
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :second_level_cache
|
23
|
-
|
24
|
-
def second_level_cache=(cache_store)
|
25
|
-
clear_cache_chain
|
26
|
-
@second_level_cache = cache_store
|
27
|
-
end
|
28
|
-
|
29
|
-
SCHEMA = {
|
30
|
-
obj_data: 'obj/#{cache_id}/#{index}/#{key}',
|
31
|
-
content_state_node: 'csn/#{content_state_id}',
|
32
|
-
workspace_state: 'wrkstt/#{workspace_id}',
|
33
|
-
tag_data: 'tagd/#{tag}'
|
34
|
-
}
|
35
|
-
|
36
|
-
SCHEMA.each do |name, schema|
|
37
|
-
params = schema.scan(/\#{([^}]+)}/).map(&:first)
|
38
|
-
params_code = params.map(&:to_s).join(",")
|
39
|
-
|
40
|
-
# using eval instead of define_method for performance reasons
|
41
|
-
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
42
|
-
def read_#{name}(#{params_code})
|
43
|
-
cache.read("#{schema}")
|
44
|
-
end
|
45
|
-
END
|
46
|
-
|
47
|
-
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
48
|
-
def write_#{name}(#{params_code}, data, **options)
|
49
|
-
if data == nil
|
50
|
-
raise InternalError, "tried to write nil into #{schema}"
|
51
|
-
end
|
52
|
-
cache.write("#{schema}", data, **options)
|
53
|
-
end
|
54
|
-
END
|
7
|
+
def self.cache
|
8
|
+
Thread.current[:scrivito_cms_data_cache_chain] ||=
|
9
|
+
Cache::RamStore.new(next_store: first_level_cache, cache_prefix: VERSION)
|
10
|
+
end
|
55
11
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
12
|
+
def self.cache_path=(path)
|
13
|
+
clear_cache_chain
|
14
|
+
@cache_path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.first_level_cache
|
18
|
+
@first_level_cache ||= Cache::FileStore.new(path: @cache_path, next_store: second_level_cache)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.second_level_cache
|
22
|
+
@second_level_cache
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.second_level_cache=(cache_store)
|
26
|
+
clear_cache_chain
|
27
|
+
@second_level_cache = cache_store
|
28
|
+
end
|
63
29
|
|
64
|
-
|
65
|
-
|
66
|
-
|
30
|
+
SCHEMA = {
|
31
|
+
obj_data: 'obj/#{cache_id}/#{index}/#{key}',
|
32
|
+
content_state_node: 'csn/#{content_state_id}',
|
33
|
+
workspace_state: 'wrkstt/#{workspace_id}',
|
34
|
+
tag_data: 'tagd/#{tag}'
|
35
|
+
}
|
36
|
+
|
37
|
+
SCHEMA.each do |name, schema|
|
38
|
+
params = schema.scan(/\#{([^}]+)}/).map(&:first)
|
39
|
+
params_code = params.map(&:to_s).join(",")
|
40
|
+
|
41
|
+
# using eval instead of define_method for performance reasons
|
42
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
43
|
+
def self.read_#{name}(#{params_code})
|
44
|
+
cache.read("#{schema}")
|
45
|
+
end
|
46
|
+
END
|
47
|
+
|
48
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
49
|
+
def self.write_#{name}(#{params_code}, data, **options)
|
50
|
+
if data == nil
|
51
|
+
raise InternalError, "tried to write nil into #{schema}"
|
52
|
+
end
|
53
|
+
cache.write("#{schema}", data, **options)
|
54
|
+
end
|
55
|
+
END
|
56
|
+
|
57
|
+
# for test purposes
|
58
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
59
|
+
def self.evict_#{name}(#{params_code})
|
60
|
+
cache.write("#{schema}", nil)
|
61
|
+
end
|
62
|
+
END
|
63
|
+
end
|
67
64
|
|
68
|
-
|
69
|
-
|
65
|
+
def self.write_data_to_tag(data)
|
66
|
+
tag = Digest::SHA1.base64digest(Marshal::dump(data))
|
67
|
+
write_tag_data(tag, data)
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
read_tag_data(tag)
|
74
|
-
end
|
69
|
+
tag
|
70
|
+
end
|
75
71
|
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
def self.read_data_from_tag(tag)
|
73
|
+
raise ArgumentError, 'tag can not be nil' if tag.nil?
|
74
|
+
read_tag_data(tag)
|
75
|
+
end
|
79
76
|
|
80
|
-
|
77
|
+
def self.clear_request_cache
|
78
|
+
cache.clear
|
79
|
+
end
|
81
80
|
|
82
|
-
|
83
|
-
|
81
|
+
private_class_method def self.clear_cache_chain
|
82
|
+
Thread.current[:scrivito_cms_data_cache_chain] = nil
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
end
|
84
|
+
@first_level_cache = nil
|
85
|
+
@second_level_cache = nil
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
@@ -84,162 +84,158 @@ module Scrivito
|
|
84
84
|
Addressable::URI::CharacterClasses::UNRESERVED)
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
private_class_method def self.request_cms_api(action, resource_path, payload, options)
|
88
|
+
assert_valid_resource_path(resource_path)
|
88
89
|
|
89
|
-
|
90
|
+
log_api_request(action, resource_path, payload) do
|
91
|
+
@number_of_requests += 1 if resource_path == @count_requests
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
+
timer = build_timer(options)
|
94
|
+
response = response_for_request_cms_api(action, resource_path, payload, timer)
|
93
95
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
if task_response?(response)
|
101
|
-
task_path = "tasks/#{response['task']['id']}"
|
102
|
-
poll_interval = options[:interval].presence.try(:to_f) || 2
|
103
|
-
wait_for_tasks_final_response(task_path, poll_interval)
|
104
|
-
else
|
105
|
-
response
|
106
|
-
end
|
96
|
+
if task_response?(response)
|
97
|
+
task_path = "tasks/#{response['task']['id']}"
|
98
|
+
poll_interval = options[:interval].presence.try(:to_f) || 2
|
99
|
+
wait_for_tasks_final_response(task_path, poll_interval)
|
100
|
+
else
|
101
|
+
response
|
107
102
|
end
|
108
103
|
end
|
104
|
+
end
|
109
105
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
106
|
+
private_class_method def self.log_api_request(action, resource_path, payload, &block)
|
107
|
+
ActiveSupport::Notifications.instrumenter.instrument(
|
108
|
+
"backend_request.scrivito",
|
109
|
+
{
|
110
|
+
:path => resource_path,
|
111
|
+
:verb => action,
|
112
|
+
:params => action == :get ? payload : nil
|
113
|
+
},
|
114
|
+
&block
|
115
|
+
)
|
116
|
+
end
|
121
117
|
|
122
|
-
|
123
|
-
|
124
|
-
|
118
|
+
private_class_method def self.task_response?(response)
|
119
|
+
response.is_a?(Hash) && response.keys == ['task'] && response['task'].is_a?(Hash)
|
120
|
+
end
|
125
121
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
122
|
+
private_class_method def self.response_for_request_cms_api(method, resource_path, payload,
|
123
|
+
timer)
|
124
|
+
request = method_to_net_http_class(method).new(path(resource_path))
|
125
|
+
set_headers(request)
|
126
|
+
request.body = MultiJson.encode(payload) if payload.present?
|
130
127
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
128
|
+
response = retry_once_on_network_error(method, timer) do
|
129
|
+
CmsRestApi::RateLimit.retry_on_rate_limit(timer) do
|
130
|
+
request_timeout = [timer.remaining_time, MAX_REQUEST_TIME].min
|
131
|
+
ConnectionManager.request(request, timeout: request_timeout)
|
136
132
|
end
|
137
|
-
|
138
|
-
handle_response(resource_path, response)
|
139
133
|
end
|
140
134
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
135
|
+
handle_response(resource_path, response)
|
136
|
+
end
|
137
|
+
|
138
|
+
private_class_method def self.handle_response(resource_path, response)
|
139
|
+
http_code = response.code.to_i
|
140
|
+
if response.code.start_with?('2')
|
141
|
+
MultiJson.load(response.body)
|
142
|
+
elsif response.code == '403'
|
143
|
+
raise AccessDenied.new(response.body)
|
144
|
+
else
|
145
|
+
begin
|
146
|
+
error_body = MultiJson.decode(response.body)
|
147
|
+
specific_output = error_body['error']
|
148
|
+
|
149
|
+
if response.code.start_with?('4')
|
150
|
+
backend_code = error_body['code']
|
151
|
+
raise ClientError.new(specific_output,
|
152
|
+
http_code: http_code, backend_code: backend_code)
|
153
|
+
elsif response.code == '500' && specific_output
|
154
|
+
raise BackendError.new(specific_output, http_code)
|
155
|
+
else # 3xx and >500 are treated as NetworkErrors
|
162
156
|
raise NetworkError.new(response.body, http_code)
|
163
157
|
end
|
158
|
+
rescue MultiJson::DecodeError
|
159
|
+
raise NetworkError.new(response.body, http_code)
|
164
160
|
end
|
165
161
|
end
|
162
|
+
end
|
166
163
|
|
167
|
-
|
168
|
-
|
164
|
+
private_class_method def self.wait_for_tasks_final_response(task_path, poll_interval)
|
165
|
+
task_data = fetch_final_response(task_path, poll_interval)
|
169
166
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
167
|
+
if task_data['status'] == 'success'
|
168
|
+
task_data['result']
|
169
|
+
else
|
170
|
+
message = task_data["message"] ||
|
171
|
+
"Missing error message in task response #{task_data}"
|
175
172
|
|
176
|
-
|
177
|
-
|
178
|
-
end
|
173
|
+
backend_code = task_data['code']
|
174
|
+
raise ClientError.new(message, backend_code: backend_code)
|
179
175
|
end
|
176
|
+
end
|
180
177
|
|
181
|
-
|
182
|
-
|
183
|
-
|
178
|
+
private_class_method def self.upload_file(file, content_type, upload_permission)
|
179
|
+
uri = URI.parse(upload_permission['url'])
|
180
|
+
uri.normalize!
|
184
181
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
182
|
+
File.open(file) do |open_file|
|
183
|
+
upload_io = UploadIO.new(open_file, content_type, File.basename(file))
|
184
|
+
params = upload_permission['fields'].merge('file' => upload_io)
|
185
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, params)
|
186
|
+
response = ConnectionManager.request(request, uri: uri)
|
190
187
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
end
|
188
|
+
if response.code.starts_with?('2')
|
189
|
+
upload_permission['blob']
|
190
|
+
else
|
191
|
+
raise ScrivitoError, "File upload failed with code #{response.code}"
|
196
192
|
end
|
197
193
|
end
|
194
|
+
end
|
198
195
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
196
|
+
private_class_method def self.fetch_final_response(task_path, poll_interval)
|
197
|
+
loop do
|
198
|
+
sleep poll_interval
|
199
|
+
task_data = response_for_request_cms_api(:get, task_path, nil, build_timer)
|
203
200
|
|
204
|
-
|
205
|
-
end
|
201
|
+
return task_data if task_data['status'] != 'open'
|
206
202
|
end
|
203
|
+
end
|
207
204
|
|
208
|
-
|
209
|
-
|
205
|
+
private_class_method def self.retry_once_on_network_error(method, timer)
|
206
|
+
return yield if method == :post
|
210
207
|
|
211
|
-
|
208
|
+
begin
|
209
|
+
yield
|
210
|
+
rescue NetworkError => e
|
211
|
+
if timer.finished?
|
212
|
+
raise e
|
213
|
+
else
|
212
214
|
yield
|
213
|
-
rescue NetworkError => e
|
214
|
-
if timer.finished?
|
215
|
-
raise e
|
216
|
-
else
|
217
|
-
yield
|
218
|
-
end
|
219
215
|
end
|
220
216
|
end
|
217
|
+
end
|
221
218
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
219
|
+
private_class_method def self.set_headers(request)
|
220
|
+
request.basic_auth('api_token', Configuration.api_key)
|
221
|
+
request['Content-type'] = 'application/json'
|
222
|
+
request['Accept'] = 'application/json'
|
223
|
+
end
|
227
224
|
|
228
|
-
|
229
|
-
|
230
|
-
|
225
|
+
private_class_method def self.path(path)
|
226
|
+
"/#{Configuration.endpoint_uri.path}/#{path}".squeeze('/')
|
227
|
+
end
|
231
228
|
|
232
|
-
|
233
|
-
|
234
|
-
|
229
|
+
private_class_method def self.build_timer(options={})
|
230
|
+
CmsRestApi::RequestTimer.new(options.fetch(:timeout, DEFAULT_TIMEOUT))
|
231
|
+
end
|
235
232
|
|
236
|
-
|
237
|
-
|
238
|
-
|
233
|
+
private_class_method def self.method_to_net_http_class(method)
|
234
|
+
METHOD_TO_NET_HTTP_CLASS.fetch(method)
|
235
|
+
end
|
239
236
|
|
240
|
-
|
241
|
-
|
242
|
-
end
|
237
|
+
private_class_method def self.assert_valid_resource_path(resource_path)
|
238
|
+
URI(resource_path)
|
243
239
|
end
|
244
240
|
end
|
245
241
|
end
|