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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/obj_class_controller.rb +12 -3
  3. data/app/controllers/scrivito/users_controller.rb +11 -0
  4. data/app/helpers/scrivito_helper.rb +4 -4
  5. data/config/ca-bundle.crt +1 -1
  6. data/lib/assets/javascripts/scrivito.js +12 -12
  7. data/lib/assets/javascripts/scrivito_ui_redirect.js +2 -2
  8. data/lib/assets/javascripts/scrivito_with_js_sdk.js +20123 -19370
  9. data/lib/assets/stylesheets/scrivito.css +1 -1
  10. data/lib/scrivito/attribute_content.rb +25 -15
  11. data/lib/scrivito/attribute_serializer.rb +3 -1
  12. data/lib/scrivito/backend/obj_load.rb +1 -5
  13. data/lib/scrivito/backend/obj_query.rb +2 -6
  14. data/lib/scrivito/backend/parent_path_index.rb +4 -7
  15. data/lib/scrivito/backend/path_index.rb +5 -8
  16. data/lib/scrivito/backend/permalink_index.rb +4 -7
  17. data/lib/scrivito/basic_obj.rb +70 -67
  18. data/lib/scrivito/basic_widget.rb +1 -0
  19. data/lib/scrivito/binary.rb +17 -2
  20. data/lib/scrivito/binary_param_verifier.rb +30 -34
  21. data/lib/scrivito/client_error.rb +2 -0
  22. data/lib/scrivito/cms_data_cache.rb +71 -73
  23. data/lib/scrivito/cms_rest_api.rb +114 -118
  24. data/lib/scrivito/cms_rest_api/rate_limit.rb +21 -26
  25. data/lib/scrivito/cms_routing.rb +1 -1
  26. data/lib/scrivito/configuration.rb +412 -373
  27. data/lib/scrivito/connection_manager.rb +42 -36
  28. data/lib/scrivito/deprecation.rb +11 -15
  29. data/lib/scrivito/diff.rb +6 -8
  30. data/lib/scrivito/gem_info.rb +9 -6
  31. data/lib/scrivito/generator_helper.rb +5 -7
  32. data/lib/scrivito/migrations/cms_backend.rb +11 -8
  33. data/lib/scrivito/migrations/migrator.rb +19 -21
  34. data/lib/scrivito/obj_search_enumerator.rb +24 -24
  35. data/lib/scrivito/request_homepage.rb +10 -14
  36. data/lib/scrivito/route.rb +27 -31
  37. data/lib/scrivito/sdk_engine.rb +8 -4
  38. data/lib/scrivito/tag_renderer.rb +17 -23
  39. data/lib/scrivito/test_request.rb +7 -9
  40. data/lib/scrivito/ui_config.rb +0 -19
  41. data/lib/scrivito/user.rb +89 -99
  42. data/lib/scrivito/workspace.rb +12 -16
  43. data/lib/scrivito/workspace_data.rb +2 -6
  44. data/lib/scrivito_sdk.rb +0 -13
  45. data/lib/tasks/cache.rake +2 -0
  46. metadata +18 -4
@@ -3,45 +3,41 @@ module Scrivito
3
3
  module BinaryParamVerifier
4
4
  InvalidSignature = Class.new(StandardError)
5
5
 
6
- class << self
7
- def verify(encrypted_params)
8
- params = decrypt(encrypted_params)
9
- expires = DateConversion.deserialize_from_backend(params['expires'])
10
- raise InvalidSignature if expires && expires < Time.zone.now
11
- verify_without_expire(encrypted_params)
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
- def verify_without_expire(encrypted_params)
15
- params = decrypt(encrypted_params)
16
- Binary.new(params['binary_id'], params['expires'].nil?,
17
- transformation_definition: params['transformation_definition'],
18
- obj_id: params['obj_id'])
19
- end
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
- def generate(binary)
22
- params = {
23
- binary_id: binary.id,
24
- obj_id: binary.obj_id,
25
- transformation_definition: binary.transformation_definition,
26
- }
27
- if binary.private?
28
- params[:expires] = DateConversion.serialize_for_backend(Time.zone.now + 1.hour)
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
- private
34
-
35
- def decrypt(encrypted_params)
36
- message_verifier.verify(encrypted_params)
37
- rescue ActiveSupport::MessageVerifier::InvalidSignature
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
- def message_verifier
42
- ActiveSupport::MessageVerifier.new(
43
- Rails.application.secrets.secret_key_base, serializer: JSON)
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
 
@@ -10,6 +10,8 @@ module Scrivito
10
10
  end
11
11
 
12
12
  def ==(client_error)
13
+ return false unless client_error.is_a?(ClientError)
14
+
13
15
  message == client_error.message &&
14
16
  http_code == client_error.http_code &&
15
17
  backend_code == client_error.backend_code
@@ -4,87 +4,85 @@ module Scrivito
4
4
  module CmsDataCache
5
5
  VERSION = 'v3'.freeze
6
6
 
7
- class << self
8
- def cache
9
- Thread.current[:scrivito_cms_data_cache_chain] ||=
10
- Cache::RamStore.new(next_store: first_level_cache, cache_prefix: VERSION)
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
- # for test purposes
57
- class_eval(<<-END, __FILE__, __LINE__ + 1)
58
- def evict_#{name}(#{params_code})
59
- cache.write("#{schema}", nil)
60
- end
61
- END
62
- end
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
- def write_data_to_tag(data)
65
- tag = Digest::SHA1.base64digest(Marshal::dump(data))
66
- write_tag_data(tag, data)
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
- tag
69
- end
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
- def read_data_from_tag(tag)
72
- raise ArgumentError, 'tag can not be nil' if tag.nil?
73
- read_tag_data(tag)
74
- end
69
+ tag
70
+ end
75
71
 
76
- def clear_request_cache
77
- cache.clear
78
- end
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
- private
77
+ def self.clear_request_cache
78
+ cache.clear
79
+ end
81
80
 
82
- def clear_cache_chain
83
- Thread.current[:scrivito_cms_data_cache_chain] = nil
81
+ private_class_method def self.clear_cache_chain
82
+ Thread.current[:scrivito_cms_data_cache_chain] = nil
84
83
 
85
- @first_level_cache = nil
86
- @second_level_cache = nil
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
- class << self
87
+ private_class_method def self.request_cms_api(action, resource_path, payload, options)
88
+ assert_valid_resource_path(resource_path)
88
89
 
89
- private
90
+ log_api_request(action, resource_path, payload) do
91
+ @number_of_requests += 1 if resource_path == @count_requests
90
92
 
91
- def request_cms_api(action, resource_path, payload, options)
92
- assert_valid_resource_path(resource_path)
93
+ timer = build_timer(options)
94
+ response = response_for_request_cms_api(action, resource_path, payload, timer)
93
95
 
94
- log_api_request(action, resource_path, payload) do
95
- @number_of_requests += 1 if resource_path == @count_requests
96
-
97
- timer = build_timer(options)
98
- response = response_for_request_cms_api(action, resource_path, payload, timer)
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
- def log_api_request(action, resource_path, payload, &block)
111
- ActiveSupport::Notifications.instrumenter.instrument(
112
- "backend_request.scrivito",
113
- {
114
- :path => resource_path,
115
- :verb => action,
116
- :params => action == :get ? payload : nil
117
- },
118
- &block
119
- )
120
- end
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
- def task_response?(response)
123
- response.is_a?(Hash) && response.keys == ['task'] && response['task'].is_a?(Hash)
124
- end
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
- def response_for_request_cms_api(method, resource_path, payload, timer)
127
- request = method_to_net_http_class(method).new(path(resource_path))
128
- set_headers(request)
129
- request.body = MultiJson.encode(payload) if payload.present?
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
- response = retry_once_on_network_error(method, timer) do
132
- CmsRestApi::RateLimit.retry_on_rate_limit(timer) do
133
- request_timeout = [timer.remaining_time, MAX_REQUEST_TIME].min
134
- ConnectionManager.request(request, timeout: request_timeout)
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
- def handle_response(resource_path, response)
142
- http_code = response.code.to_i
143
- if response.code.start_with?('2')
144
- MultiJson.load(response.body)
145
- elsif response.code == '403'
146
- raise AccessDenied.new(response.body)
147
- else
148
- begin
149
- error_body = MultiJson.decode(response.body)
150
- specific_output = error_body['error']
151
-
152
- if response.code.start_with?('4')
153
- backend_code = error_body['code']
154
- raise ClientError.new(specific_output,
155
- http_code: http_code, backend_code: backend_code)
156
- elsif response.code == '500' && specific_output
157
- raise BackendError.new(specific_output, http_code)
158
- else # 3xx and >500 are treated as NetworkErrors
159
- raise NetworkError.new(response.body, http_code)
160
- end
161
- rescue MultiJson::DecodeError
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
- def wait_for_tasks_final_response(task_path, poll_interval)
168
- task_data = fetch_final_response(task_path, poll_interval)
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
- if task_data['status'] == 'success'
171
- task_data['result']
172
- else
173
- message = task_data["message"] ||
174
- "Missing error message in task response #{task_data}"
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
- backend_code = task_data['code']
177
- raise ClientError.new(message, backend_code: backend_code)
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
- def upload_file(file, content_type, upload_permission)
182
- uri = URI.parse(upload_permission['url'])
183
- uri.normalize!
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
- File.open(file) do |open_file|
186
- upload_io = UploadIO.new(open_file, content_type, File.basename(file))
187
- params = upload_permission['fields'].merge('file' => upload_io)
188
- request = Net::HTTP::Post::Multipart.new(uri.path, params)
189
- response = ConnectionManager.request(request, uri: uri)
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
- if response.code.starts_with?('2')
192
- upload_permission['blob']
193
- else
194
- raise ScrivitoError, "File upload failed with code #{response.code}"
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
- def fetch_final_response(task_path, poll_interval)
200
- loop do
201
- sleep poll_interval
202
- task_data = response_for_request_cms_api(:get, task_path, nil, build_timer)
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
- return task_data if task_data['status'] != 'open'
205
- end
201
+ return task_data if task_data['status'] != 'open'
206
202
  end
203
+ end
207
204
 
208
- def retry_once_on_network_error(method, timer)
209
- return yield if method == :post
205
+ private_class_method def self.retry_once_on_network_error(method, timer)
206
+ return yield if method == :post
210
207
 
211
- begin
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
- def set_headers(request)
223
- request.basic_auth('api_token', Configuration.api_key)
224
- request['Content-type'] = 'application/json'
225
- request['Accept'] = 'application/json'
226
- end
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
- def path(path)
229
- "/#{Configuration.endpoint_uri.path}/#{path}".squeeze('/')
230
- end
225
+ private_class_method def self.path(path)
226
+ "/#{Configuration.endpoint_uri.path}/#{path}".squeeze('/')
227
+ end
231
228
 
232
- def build_timer(options={})
233
- CmsRestApi::RequestTimer.new(options.fetch(:timeout, DEFAULT_TIMEOUT))
234
- end
229
+ private_class_method def self.build_timer(options={})
230
+ CmsRestApi::RequestTimer.new(options.fetch(:timeout, DEFAULT_TIMEOUT))
231
+ end
235
232
 
236
- def method_to_net_http_class(method)
237
- METHOD_TO_NET_HTTP_CLASS.fetch(method)
238
- end
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
- def assert_valid_resource_path(resource_path)
241
- URI(resource_path)
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