Skip to content
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

Merged
merged 5 commits into from
Apr 12, 2018
Merged

Conversation

miah
Copy link
Contributor

@miah miah commented Apr 10, 2018

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.

image

Signed-off-by: Miah Johnson [email protected]

Fixes #2913

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]>
@miah miah requested a review from a team as a code owner April 10, 2018 18:11
@miah miah changed the title Ensure @params always has a valid value. Ensure @params in shadow resource always has a valid value. Apr 10, 2018
Copy link
Contributor

@jerryaldrichiii jerryaldrichiii left a 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!

Copy link
Contributor

@TrevorBramble TrevorBramble left a 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! ;^)

@@ -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
Copy link
Contributor

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

Copy link
Contributor

@TrevorBramble TrevorBramble Apr 10, 2018

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

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@@ -121,10 +120,14 @@ def to_s
"/etc/shadow#{f}"
end

def params
@params = @lines.nil? ? [] : @lines.map { |l| parse_shadow_line(l) }
end
Copy link
Contributor

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.

private

def map_data(id)
@params.map { |x| x[id] }
params.map { |x| x[id] }
Copy link
Contributor

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.)

@@ -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
Copy link
Contributor

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. =^)

miah added 2 commits April 10, 2018 15:13
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 = {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!


def initialize(path = '/etc/shadow', opts = nil)
opts ||= {}
def initialize(path = '/etc/shadow', opts = {})
@path = path || '/etc/shadow'
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@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)
Copy link
Contributor

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.

miah added 2 commits April 12, 2018 11:20
Update test output to match updated entries.

Signed-off-by: Miah Johnson <[email protected]>
Copy link
Contributor

@jquick jquick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @miah

@jquick jquick added the Type: Enhancement Improves an existing feature label Apr 12, 2018
@jquick jquick merged commit b40e553 into master Apr 12, 2018
@jquick jquick deleted the miah/2913 branch April 12, 2018 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Enhancement Improves an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants