-
Notifications
You must be signed in to change notification settings - Fork 679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure @params in shadow resource always has a valid value. #2939
Conversation
unreadable shadow files. Ensure @params always has a safe value, otherwise we may stacktrace when unable to read /etc/shadow and invoked with method chaining. Signed-off-by: Miah Johnson <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Thanks @miah!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You asked for it! ;^)
lib/resources/shadow.rb
Outdated
@@ -43,7 +42,7 @@ def initialize(path = '/etc/shadow', opts = nil) | |||
@filters = opts[:filters] || '' | |||
@raw_content = opts[:content] || read_file_content(@path, allow_empty: true) | |||
@lines = @raw_content.to_s.split("\n") | |||
@params = @lines.map { |l| parse_shadow_line(l) } | |||
params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really rather see this assignment preserved, rather than treating the setup as a side-effect of calling a method.
def initialize
@params = set_params(@lines)
end
def set_params(lines)
lines.nil? ? [] : lines.map { |l| parse_shadow_line(1) }
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we're there, @raw_content
and @lines
are only used in the initializer (if you pass @lines
to the params
method) so why not make them local variables instead? You could even inline the lines parsing because there's no conditional logic involved.
def initialize(path = '/etc/shadow', opts = nil)
# ...
params(raw_content.to_s.split("\n"))
end
...And while we're there
def initialize(path = '/etc/shadow', opts = nil)
opts ||= {}
# ...
end
This makes me so sad. Would you mind?
def initialize(path = '/etc/shadow', opts = {})
# ...
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this @params = params(@raw_content.to_s.split("\n"))
but when I set up our params this way, when filter
is called I'm still getting a nil value.
NoMethodError: undefined method `find_all' for nil:NilClass
/Users/miah/projects/github/tmp/inspec/lib/resources/shadow.rb:80:in `block in filter'
So what I've done is preserved @lines
and reference that from set_params.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed def lines
elsewhere. Still, I'd pass it into the new method that makes your params and use it as a local variable there.
lib/resources/shadow.rb
Outdated
@@ -121,10 +120,14 @@ def to_s | |||
"/etc/shadow#{f}" | |||
end | |||
|
|||
def params | |||
@params = @lines.nil? ? [] : @lines.map { |l| parse_shadow_line(l) } | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of directions this could go to make the point more obvious.
You could separate the guard statement:
def params(lines)
return [] if lines.nil?
lines.map { |l| parse_shadow_line(1) }
end
And that maps directly to your logic in the ternary. But what I think you're actually concerned about is calling map
on nil
? So this would actually be more direct and clear:
def params(lines)
Array(lines).map { |l| parse_shadow_line(1) }
end
Array(nil)
will produce the empty array, and pass through the map
call to return what you expect, just that empty array. So there's one path through the code and all the needed safety.
lib/resources/shadow.rb
Outdated
private | ||
|
||
def map_data(id) | ||
@params.map { |x| x[id] } | ||
params.map { |x| x[id] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using collect
here instead of map
to make it clear that you're collecting a new set of values from the input data. (Same for the parsed shadow lines in the params
method.)
test/unit/resources/shadow_test.rb
Outdated
@@ -86,6 +86,24 @@ | |||
end | |||
end | |||
|
|||
describe 'when method chained' do | |||
let(:unreadable_shadow) { load_resource('shadow', '/etc/unreadable_shadow') } | |||
it 'can read /etc/shadow and #filter matches user with no password and inactive_days' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate sections with newlines please. =^)
output. Added some missing newlines. Catch deprecation notice on `lines`. Signed-off-by: Miah Johnson <[email protected]>
Deprecate `lines`; its really only used internally but it was 'exposed' through tests and who knows if there is external use. `lines` is not documented as a property at least.. `#set_params` is much better now =) Signed-off-by: Miah Johnson <[email protected]>
|
||
def initialize(path = '/etc/shadow', opts = nil) | ||
opts ||= {} | ||
def initialize(path = '/etc/shadow', opts = {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
lib/resources/shadow.rb
Outdated
|
||
def initialize(path = '/etc/shadow', opts = nil) | ||
opts ||= {} | ||
def initialize(path = '/etc/shadow', opts = {}) | ||
@path = path || '/etc/shadow' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if we could check if the file exist and throw an exception here? Otherwise we absorb the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely can do this, but the file resource we use for reading and content gathering also does this; but we do absorb the error when method chained.
lib/resources/shadow.rb
Outdated
@raw_content = opts[:content] || read_file_content(@path, allow_empty: true) | ||
@lines = @raw_content.to_s.split("\n") | ||
@params = @lines.map { |l| parse_shadow_line(l) } | ||
raw_content = opts[:content] || read_file_content(@path, allow_empty: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be beneficial to move the read logic out of the initialize function and do this during runtime not object creation time. This would allow us to do less during inspec check
runs.
Signed-off-by: Miah Johnson <[email protected]>
Update test output to match updated entries. Signed-off-by: Miah Johnson <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @miah
Add tests for method chained shadow resource with readable and unreadable shadow files.
Ensure @params always has a safe value, otherwise we may stacktrace when
unable to read /etc/shadow and invoked with method chaining.
My only complaint with this is that when the
/etc/shadow
file is unreadable, you won't see the error when chaining; you'll need to test the file or a specific user.Signed-off-by: Miah Johnson [email protected]
Fixes #2913