Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-04-04 08:44:46 UTC using RuboCop version 1.48.1.
# on 2023-08-25 10:33:19 UTC using RuboCop version 1.48.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -34,12 +34,12 @@ Lint/MissingCopEnableDirective:
- 'lib/puppet-lint/tasks/puppet-lint.rb'
- 'spec/unit/puppet-lint/puppet-lint_spec.rb'

# Offense count: 58
# Offense count: 60
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 142

# Offense count: 34
# Offense count: 36
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
# AllowedMethods: refine
Metrics/BlockLength:
Expand All @@ -55,22 +55,27 @@ Metrics/BlockNesting:
Metrics/ClassLength:
Max: 387

# Offense count: 33
# Offense count: 35
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 33

# Offense count: 80
# Offense count: 82
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 108

# Offense count: 26
# Offense count: 27
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 31

# Offense count: 182
# Offense count: 1
Naming/AccessorMethodName:
Exclude:
- 'lib/puppet-lint/plugins/check_unsafe_interpolations/check_unsafe_interpolations.rb'

# Offense count: 186
# Configuration parameters: ForbiddenDelimiters.
# ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$))
Naming/HeredocDelimiterNaming:
Expand All @@ -92,13 +97,13 @@ Performance/CollectionLiteralInLoop:
- 'lib/puppet-lint/plugins/check_resources/ensure_first_param.rb'
- 'lib/puppet-lint/plugins/check_whitespace/trailing_whitespace.rb'

# Offense count: 406
# Offense count: 417
# Configuration parameters: Prefixes, AllowedPatterns.
# Prefixes: when, with, without
RSpec/ContextWording:
Enabled: false

# Offense count: 41
# Offense count: 42
# Configuration parameters: IgnoredMetadata.
RSpec/DescribeClass:
Enabled: false
Expand All @@ -122,7 +127,7 @@ RSpec/FilePath:
- 'spec/unit/puppet-lint/lexer_spec.rb'
- 'spec/unit/puppet-lint/puppet-lint_spec.rb'

# Offense count: 138
# Offense count: 139
RSpec/MultipleExpectations:
Max: 137

Expand All @@ -136,14 +141,21 @@ RSpec/MultipleMemoizedHelpers:
RSpec/NestedGroups:
Max: 5

# Offense count: 8
# Offense count: 10
RSpec/RepeatedExampleGroupDescription:
Exclude:
- 'spec/unit/puppet-lint/plugins/check_resources/file_mode_spec.rb'
- 'spec/unit/puppet-lint/plugins/check_resources/unquoted_file_mode_spec.rb'
- 'spec/unit/puppet-lint/plugins/check_unsafe_interpolations/check_unsafe_interpolations_spec.rb'
- 'spec/unit/puppet-lint/plugins/legacy_facts/legacy_facts_spec.rb'

# Offense count: 106
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/ConcatArrayLiterals:
Exclude:
- 'lib/puppet-lint/plugins/check_unsafe_interpolations/check_unsafe_interpolations.rb'

# Offense count: 108
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
COMMANDS = Array['command', 'onlyif', 'unless']
INTERPOLATED_STRINGS = Array[:DQPRE, :DQMID]
USELESS_CHARS = Array[:WHITESPACE, :COMMA]

PuppetLint.new_check(:check_unsafe_interpolations) do
def check
# Gather any exec commands' resources into an array
exec_resources = resource_indexes.filter_map do |resource|
resource_parameters = resource[:param_tokens].map(&:value)
resource if resource[:type].value == 'exec' && !(COMMANDS & resource_parameters).empty?
end

# Iterate over title tokens and raise a warning if any are variables
unless get_exec_titles.empty?
get_exec_titles.each do |title|
check_unsafe_title(title)
end
end

# Iterate over each command found in any exec
exec_resources.each do |command_resources|
check_unsafe_interpolations(command_resources)
end
end

# Iterate over the tokens in a title and raise a warning if an interpolated variable is found
def check_unsafe_title(title)
title.each do |token|
notify_warning(token.next_code_token) if interpolated?(token)
end
end

# Iterates over an exec resource and if a command, onlyif or unless paramter is found, it is checked for unsafe interpolations
def check_unsafe_interpolations(command_resources)
command_resources[:tokens].each do |token|
# Skip iteration if token isn't a command of type :NAME
next unless COMMANDS.include?(token.value) && token.type == :NAME
# Don't check the command if it is parameterised
next if parameterised?(token)

check_command(token).each do |t|
notify_warning(t)
end
end
end

# Raises a warning given a token and message
def notify_warning(token)
notify :warning,
message: "unsafe interpolation of variable '#{token.value}' in exec command",
line: token.line,
column: token.column
end

# Iterates over the tokens in a command and adds it to an array of violations if it is an input variable
def check_command(token)
# Initialise variables needed in while loop
rule_violations = []
current_token = token

# Iterate through tokens in command
while current_token.type != :NEWLINE
# Check if token is a varibale and if it is parameterised
rule_violations.append(current_token.next_code_token) if interpolated?(current_token)
current_token = current_token.next_token
end

rule_violations
end

# A command is parameterised if its args are placed in an array
# This function checks if the current token is a :FARROW and if so, if it is followed by an LBRACK
def parameterised?(token)
current_token = token
while current_token.type != :NEWLINE
Copy link

Choose a reason for hiding this comment

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

Puppet doesn't care about whitespace, so why does this matter? Don't you want to look for a comma or } instead, which signifies that the next attribute comes or the definition is over.

Copy link
Author

Choose a reason for hiding this comment

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

i'm not sure what you mean. This function is used before checking the contents of the command to check if the command is parameterised. Because if so, the command won't need checked. Parameterising the command is one of the ways of making string interpolation safer

return true if current_token.type == :FARROW && current_token.next_token.next_token.type == :LBRACK
Copy link

Choose a reason for hiding this comment

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

I don't think this is valid. There may be multiple tokens between, like whitespace.


current_token = current_token.next_token
end
end

# This function is a replacement for puppet_lint's title_tokens function which assumes titles have single quotes
# This function adds a check for titles in double quotes where there could be interpolated variables
def get_exec_titles
result = []
tokens.each_with_index do |_token, token_idx|
next if tokens[token_idx].value != 'exec'

# We have a resource declaration. Now find the title
tokens_array = []
# Check if title is an array
if tokens[token_idx]&.next_code_token&.next_code_token&.type == :LBRACK
# Get the start and end indices of the array of titles
array_start_idx = tokens.rindex { |r| r.type == :LBRACK }
array_end_idx = tokens.rindex { |r| r.type == :RBRACK }

# Grab everything within the array
title_array_tokens = tokens[(array_start_idx + 1)..(array_end_idx - 1)]
tokens_array.concat(title_array_tokens.reject do |token|
USELESS_CHARS.include?(token.type)
end)
result << tokens_array
# Check if title is double quotes string
elsif tokens[token_idx].next_code_token.next_code_token.type == :DQPRE
# Find the start and end of the title
title_start_idx = tokens.find_index(tokens[token_idx].next_code_token.next_code_token)
title_end_idx = title_start_idx + index_offset_for(':', tokens[title_start_idx..tokens.length])

result << tokens[title_start_idx..title_end_idx]
# Title is in single quotes
else
tokens_array.concat([tokens[token_idx].next_code_token.next_code_token])

result << tokens_array
end
end
result
end

def interpolated?(token)
INTERPOLATED_STRINGS.include?(token.type)
end

# Finds the index offset of the next instance of `value` in `tokens_slice` from the original index
def index_offset_for(value, tokens_slice)
tokens_slice.each_with_index do |token, i|
return i if value.include?(token.value)
end
end
end
Loading