2022-03-08 04:17:44 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
RSpec.describe SensitiveSerializableHash do
|
|
|
|
describe '.prevent_from_serialization' do
|
2022-06-13 14:09:22 -04:00
|
|
|
let(:base_class) do
|
2022-03-08 04:17:44 -05:00
|
|
|
Class.new do
|
|
|
|
include ActiveModel::Serialization
|
|
|
|
include SensitiveSerializableHash
|
2022-06-13 14:09:22 -04:00
|
|
|
end
|
|
|
|
end
|
2022-03-08 04:17:44 -05:00
|
|
|
|
2022-06-13 14:09:22 -04:00
|
|
|
let(:test_class) do
|
|
|
|
Class.new(base_class) do
|
2022-03-08 04:17:44 -05:00
|
|
|
attr_accessor :name, :super_secret
|
|
|
|
|
|
|
|
prevent_from_serialization :super_secret
|
|
|
|
|
|
|
|
def attributes
|
|
|
|
{ 'name' => nil, 'super_secret' => nil }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-13 14:09:22 -04:00
|
|
|
let(:another_class) do
|
|
|
|
Class.new(base_class) do
|
|
|
|
prevent_from_serialization :sub_secret
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-08 04:17:44 -05:00
|
|
|
let(:model) { test_class.new }
|
|
|
|
|
|
|
|
it 'does not include the field in serializable_hash' do
|
|
|
|
expect(model.serializable_hash).not_to include('super_secret')
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'unsafe_serialization_hash option' do
|
|
|
|
it 'includes the field in serializable_hash' do
|
|
|
|
expect(model.serializable_hash(unsafe_serialization_hash: true)).to include('super_secret')
|
|
|
|
end
|
|
|
|
end
|
2022-06-13 14:09:22 -04:00
|
|
|
|
|
|
|
it 'does not change parent class attributes_exempt_from_serializable_hash' do
|
|
|
|
expect(test_class.attributes_exempt_from_serializable_hash).to contain_exactly(:super_secret)
|
|
|
|
expect(another_class.attributes_exempt_from_serializable_hash).to contain_exactly(:sub_secret)
|
|
|
|
end
|
2022-03-08 04:17:44 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
describe '#serializable_hash' do
|
|
|
|
shared_examples "attr_encrypted attribute" do |klass, attribute_name|
|
|
|
|
context "#{klass.name}\##{attribute_name}" do
|
|
|
|
let(:attributes) { [attribute_name, "encrypted_#{attribute_name}", "encrypted_#{attribute_name}_iv"] }
|
|
|
|
|
|
|
|
it 'has a encrypted_attributes field' do
|
|
|
|
expect(klass.encrypted_attributes).to include(attribute_name.to_sym)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not include the attribute in serializable_hash', :aggregate_failures do
|
|
|
|
attributes.each do |attribute|
|
|
|
|
expect(model.attributes).to include(attribute) # double-check the attribute does exist
|
|
|
|
|
|
|
|
expect(model.serializable_hash).not_to include(attribute)
|
|
|
|
expect(model.to_json).not_to include(attribute)
|
|
|
|
expect(model.as_json).not_to include(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'unsafe_serialization_hash option' do
|
|
|
|
it 'includes the field in serializable_hash' do
|
|
|
|
attributes.each do |attribute|
|
|
|
|
expect(model.attributes).to include(attribute) # double-check the attribute does exist
|
|
|
|
|
2022-06-10 17:09:35 -04:00
|
|
|
# Do not expect binary columns to appear in JSON
|
|
|
|
next if klass.columns_hash[attribute]&.type == :binary
|
|
|
|
|
2022-03-08 04:17:44 -05:00
|
|
|
expect(model.serializable_hash(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
expect(model.to_json(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
expect(model.as_json(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-10 17:09:35 -04:00
|
|
|
context 'for a web hook' do
|
2022-03-08 04:17:44 -05:00
|
|
|
let_it_be(:model) { create(:system_hook) }
|
2022-06-10 17:09:35 -04:00
|
|
|
|
|
|
|
it_behaves_like 'attr_encrypted attribute', WebHook, 'token'
|
|
|
|
it_behaves_like 'attr_encrypted attribute', WebHook, 'url'
|
|
|
|
it_behaves_like 'attr_encrypted attribute', WebHook, 'url_variables'
|
2022-03-08 04:17:44 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'attr_encrypted attribute', Ci::InstanceVariable, 'value' do
|
|
|
|
let_it_be(:model) { create(:ci_instance_variable) }
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples "add_authentication_token_field attribute" do |klass, attribute_name, encrypted_attribute: true, digest_attribute: false|
|
|
|
|
context "#{klass.name}\##{attribute_name}" do
|
|
|
|
let(:attributes) do
|
|
|
|
if digest_attribute
|
|
|
|
["#{attribute_name}_digest"]
|
|
|
|
elsif encrypted_attribute
|
|
|
|
[attribute_name, "#{attribute_name}_encrypted"]
|
|
|
|
else
|
|
|
|
[attribute_name]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has a add_authentication_token_field field' do
|
|
|
|
expect(klass.token_authenticatable_fields).to include(attribute_name.to_sym)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not include the attribute in serializable_hash', :aggregate_failures do
|
|
|
|
attributes.each do |attribute|
|
|
|
|
expect(model.attributes).to include(attribute) # double-check the attribute does exist
|
|
|
|
|
|
|
|
expect(model.serializable_hash).not_to include(attribute)
|
|
|
|
expect(model.to_json).not_to include(attribute)
|
|
|
|
expect(model.as_json).not_to include(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'unsafe_serialization_hash option' do
|
|
|
|
it 'includes the field in serializable_hash' do
|
|
|
|
attributes.each do |attribute|
|
|
|
|
expect(model.attributes).to include(attribute) # double-check the attribute does exist
|
|
|
|
|
|
|
|
expect(model.serializable_hash(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
expect(model.to_json(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
expect(model.as_json(unsafe_serialization_hash: true)).to include(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'add_authentication_token_field attribute', Ci::Runner, 'token' do
|
|
|
|
let_it_be(:model) { create(:ci_runner) }
|
|
|
|
|
|
|
|
it 'does not include token_expires_at in serializable_hash' do
|
|
|
|
attribute = 'token_expires_at'
|
|
|
|
|
|
|
|
expect(model.attributes).to include(attribute) # double-check the attribute does exist
|
|
|
|
|
|
|
|
expect(model.serializable_hash).not_to include(attribute)
|
|
|
|
expect(model.to_json).not_to include(attribute)
|
|
|
|
expect(model.as_json).not_to include(attribute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'add_authentication_token_field attribute', ApplicationSetting, 'health_check_access_token', encrypted_attribute: false do
|
|
|
|
# health_check_access_token_encrypted column does not exist
|
|
|
|
let_it_be(:model) { create(:application_setting) }
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'add_authentication_token_field attribute', PersonalAccessToken, 'token', encrypted_attribute: false, digest_attribute: true do
|
|
|
|
# PersonalAccessToken only has token_digest column
|
|
|
|
let_it_be(:model) { create(:personal_access_token) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|