module Scrivito class Workspace class PublishChecker < Struct.new(:workspace, :user) MAX_EXECUTION_TIME = (0.5).second def passing_certificates?(certificates) begin responses = decrypt_responses(certificates).map do |response| response.with_indifferent_access end return false unless responses.all? do |response| raise_invalid_certificates_error if response[:content_state_id].nil? response[:content_state_id] == workspace.content_state_id end assert_passing_result(responses) true rescue ActiveSupport::MessageVerifier::InvalidSignature raise_invalid_certificates_error end end def call(offset) batch_size = Configuration.check_batch_size objs, changed_obj_count = fetch_objs_and_count(offset, batch_size) passed, objs = compute_publishable(objs) if passed passing_result(objs, changed_obj_count, offset) else failing_result end end private def decrypt_responses(certificates) certificates.map(&message_verifier.method(:verify)) end def fetch_objs_and_count(offset, batch_size) enumerator = workspace.objs.changes(offset, batch_size) [enumerator.take(batch_size), enumerator.size] end def passing_result(objs, changed_obj_count, offset) until_element = offset + objs.size - 1 until_element = 'END' if until_element >= (changed_obj_count - 1) pass_sub_hash = { workspace_id: workspace.id, content_state_id: workspace.content_state_id, from: offset, until: until_element, } { result: 'pass', pass: pass_sub_hash, certificate: sign(pass_sub_hash) } end def compute_publishable(objs) start_time = Time.now result = [] objs.each do |obj| if publishable?(obj) result << obj else return false, [] end break if (Time.now - start_time) > MAX_EXECUTION_TIME end return true, result end def failing_result {result: 'fail' } end def publishable?(obj) obj.publishable? && user.can_publish?(obj) end def sign(object) message_verifier.generate(object) end def message_verifier @message_verifier ||= ActiveSupport::MessageVerifier.new( Rails.application.secrets.secret_key_base, serializer: JSON ) end def assert_passing_result(responses) sorted_responses = responses.sort_by do |response| response[:from].to_i end last_until = sorted_responses.inject(-1) do |inner_last_until, response| if inner_last_until == 'END' raise_invalid_certificates_error elsif (inner_last_until + 1) == response[:from].to_i if response[:until] == 'END' 'END' else response[:until].to_i end else raise_invalid_certificates_error end end if last_until != 'END' raise_invalid_certificates_error end end def raise_invalid_certificates_error raise ScrivitoError, 'Invalid certificates' end end end end