Skip to content

Commit

Permalink
Merge 2203f2d into 8313e89
Browse files Browse the repository at this point in the history
  • Loading branch information
jreidinger committed Apr 27, 2021
2 parents 8313e89 + 2203f2d commit 8d68e68
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 1 deletion.
85 changes: 85 additions & 0 deletions library/general/src/lib/yast2/secret_attributes.rb
@@ -0,0 +1,85 @@
# Copyright (c) [2017] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

module Yast2
# Mixin that enables a class to define attributes that are never exposed via
# #inspect, #to_s or similar methods, with the goal of preventing
# unintentional leaks of sensitive information in the application logs.
module SecretAttributes
# Inner class to store the value of the attribute without exposing it
# directly
class Attribute
attr_reader :value

def initialize(value)
@value = value
end

def to_s
value.nil? ? "" : "<secret>"
end

def inspect
value.nil? ? "nil" : "<secret>"
end

def instance_variables
# This adds even an extra barrier, just in case some formatter tries to
# use deep instrospection
[]
end
end

# Class methods for the mixin
module ClassMethods
# Similar to .attr_accessor but with additional mechanisms to prevent
# exposing the internal value of the attribute
#
# @example
# class TheClass
# include Yast2::SecretAttributes
#
# attr_accessor :name
# secret_attr :password
# end
#
# one_object = TheClass.new
# one_object.name = "Aa"
# one_object.password = "42"
#
# one_object.password # => "42"
# one_object.inspect # => "#<TheClass:0x0f8 @password=<secret>, @name=\"Aa"\">"
def secret_attr(name)
define_method(:"#{name}") do
attribute = instance_variable_get(:"@#{name}")
attribute ? attribute.value : nil
end

define_method(:"#{name}=") do |value|
instance_variable_set(:"@#{name}", Attribute.new(value))
value
end
end
end

def self.included(base)
base.extend(ClassMethods)
end
end
end
190 changes: 190 additions & 0 deletions library/general/test/yast2/secret_attributes_test.rb
@@ -0,0 +1,190 @@
#!/usr/bin/env rspec
# Copyright (c) [2017] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require_relative "../test_helper"
require "yast2/secret_attributes"
require "pp"

describe Yast2::SecretAttributes do
# Dummy test clase
class ClassWithPassword
include Yast2::SecretAttributes

attr_accessor :name
secret_attr :password
end

# Another dummy test clase
class ClassWithData
include Yast2::SecretAttributes

attr_accessor :name
secret_attr :data
end

# Hypothetical custom formatter that uses instrospection to directly query the
# internal state of the object, ignoring the uniform access principle.
def custom_formatter(object)
object.instance_variables.each_with_object("") do |var, result|
result << "@#{var}: #{object.instance_variable_get(var)};\n"
end
end

let(:with_password) { ClassWithPassword.new }
let(:with_password2) { ClassWithPassword.new }
let(:with_data) { ClassWithData.new }
let(:ultimate_hash) { { ultimate_question: 42 } }

describe ".secret_attr" do
it "provides a getter returning nil by default" do
expect(with_password.password).to be_nil
expect(with_data.data).to be_nil
expect(with_data.send(:data)).to be_nil
end

it "provides a setter" do
with_password.password = "super-secret"
expect(with_password.password).to eq "super-secret"
expect(with_password.send(:password)).to eq "super-secret"
end

it "only adds the setter and getter to the correct class" do
expect { with_password.data }.to raise_error NoMethodError
expect { with_data.password }.to raise_error NoMethodError
expect { with_password.data = 2 }.to raise_error NoMethodError
expect { with_data.password = "xx" }.to raise_error NoMethodError
end

it "does not mess attributes of different instances" do
with_password.password = "super-secret"
with_password2.password = "not so secret"
expect(with_password.password).to eq "super-secret"
expect(with_password2.password).to eq "not so secret"
end

it "does not modify #inspect for the attribute" do
expect(with_data.data.inspect).to eq "nil"

with_data.data = ultimate_hash

expect(with_data.data.inspect).to eq ultimate_hash.inspect
end

it "does not modify #to_s for the attribute" do
expect(with_data.data.to_s).to eq ""

with_data.data = ultimate_hash

expect(with_data.data.to_s).to eq ultimate_hash.to_s
expect(with_data.send(:data).to_s).to eq ultimate_hash.to_s
end

it "does not modify interpolation for the attribute" do
expect("String: #{with_data.data}").to eq "String: "

with_data.data = ultimate_hash

expect("String: #{with_data.data}").to eq "String: #{ultimate_hash}"
end

it "is copied in dup just like .attr_accessor" do
with_password.name = "data1"
with_password.password = "xxx"
duplicate = with_password.dup

expect(duplicate.name).to eq "data1"
expect(duplicate.password).to eq "xxx"

duplicate.password = "yyy"
expect(duplicate.password).to eq "yyy"
expect(with_password.password).to eq "xxx"

with_password2.name = "data2"
with_password2.password = "xx2"
duplicate2 = with_password2.dup
duplicate2.name.concat("X")
duplicate2.password.concat("X")

expect(with_password2.name).to eq "data2X"
expect(with_password2.password).to eq "xx2X"
end

context "when the attribute has never been set" do
it "is not displayed in #inspect (like .attr_accessor)" do
expect(with_password.inspect).to_not include "@name"
expect(with_password.inspect).to_not include "@password"
end

it "is not displayed by pp (like .attr_accessor)" do
expect(with_password.inspect).to_not include "@name"
expect(with_password.inspect).to_not include "@password"
end

it "is not exposed to formatters directly inspecting the internal state" do
expect(custom_formatter(with_password)).to_not include "@name:"
expect(custom_formatter(with_password)).to_not include "@password:"
end
end

context "when the attribute has been set to nil" do
before do
with_password.name = nil
with_password.password = nil
end

it "is displayed as nil in #inspect (like .attr_accessor)" do
expect(with_password.inspect).to include "@name=nil"
expect(with_password.inspect).to include "@password=nil"
end

it "is displayed as nil by pp (like .attr_accessor)" do
expect(with_password.inspect).to include "@name=nil"
expect(with_password.inspect).to include "@password=nil"
end

it "is reported as empty to formatters directly inspecting the internal state" do
expect(custom_formatter(with_password)).to include "@name:"
expect(custom_formatter(with_password)).to include "@password:"
end
end

context "when the attribute has a value" do
before do
with_password.name = "Skroob"
with_password.password = "12345"
end

it "is hidden in #inspect" do
expect(with_password.inspect).to include "@name=\"Skroob\""
expect(with_password.inspect).to include "@password=<secret>"
end

it "is hidden to pp" do
expect(with_password.inspect).to include "@name=\"Skroob\""
expect(with_password.inspect).to include "@password=<secret>"
end

it "is hidden from formatters directly inspecting the internal state" do
expect(custom_formatter(with_password)).to include "@name: Skroob;"
expect(custom_formatter(with_password)).to include "@password: <secret>;"
end
end
end
end
7 changes: 7 additions & 0 deletions package/yast2.changes
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Tue Apr 27 10:51:35 UTC 2021 - Josef Reidinger <jreidinger@suse.com>

- Add to yast2 mixin Yast2::SecretAttributes for hiding sensitive
information (bsc#1141017)
- 4.4.2

-------------------------------------------------------------------
Thu Apr 22 06:35:11 UTC 2021 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down
2 changes: 1 addition & 1 deletion package/yast2.spec
Expand Up @@ -17,7 +17,7 @@


Name: yast2
Version: 4.4.1
Version: 4.4.2
Release: 0
Summary: YaST2 Main Package
License: GPL-2.0-only
Expand Down

0 comments on commit 8d68e68

Please sign in to comment.