Module: Vmpooler::API::InputValidator

Included in:
Helpers
Defined in:
lib/vmpooler/api/input_validator.rb

Overview

Input validation helpers to enhance security

Defined Under Namespace

Classes: ValidationError

Constant Summary collapse

MAX_HOSTNAME_LENGTH =

Maximum lengths to prevent abuse

253
MAX_TAG_KEY_LENGTH =
50
MAX_TAG_VALUE_LENGTH =
255
MAX_REASON_LENGTH =
500
MAX_POOL_NAME_LENGTH =
100
MAX_TOKEN_LENGTH =
64
HOSTNAME_PATTERN =

Valid patterns

/\A[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)* \z/ix.freeze
POOL_NAME_PATTERN =
/\A[a-zA-Z0-9_-]+\z/.freeze
TAG_KEY_PATTERN =
/\A[a-zA-Z0-9_\-.]+\z/.freeze
TOKEN_PATTERN =
/\A[a-zA-Z0-9\-_]+\z/.freeze
INTEGER_PATTERN =
/\A\d+\z/.freeze

Instance Method Summary collapse

Instance Method Details

#sanitize_json_body(body) ⇒ Object

Sanitize JSON body to prevent injection



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/vmpooler/api/input_validator.rb', line 119

def sanitize_json_body(body)
  return {} if body.nil? || body.empty?

  begin
    parsed = JSON.parse(body)
    return error_response('Request body must be a JSON object') unless parsed.is_a?(Hash)

    # Limit depth and size to prevent DoS
    return error_response('Request body too complex') if json_depth(parsed) > 5
    return error_response('Request body too large') if body.length > 10_240 # 10KB max

    parsed
  rescue JSON::ParserError => e
    error_response("Invalid JSON: #{e.message}")
  end
end

#validate_disk_size(size) ⇒ Object

Validate disk size



91
92
93
94
95
96
# File 'lib/vmpooler/api/input_validator.rb', line 91

def validate_disk_size(size)
  validated = validate_integer(size, 'Disk size', min: 1, max: 2048)
  return validated if validated.is_a?(Hash) # error response

  validated
end

#validate_hostname(hostname) ⇒ Object

Validate hostname format and length



25
26
27
28
29
30
31
# File 'lib/vmpooler/api/input_validator.rb', line 25

def validate_hostname(hostname)
  return error_response('Hostname is required') if hostname.nil? || hostname.empty?
  return error_response('Hostname too long') if hostname.length > MAX_HOSTNAME_LENGTH
  return error_response('Invalid hostname format') unless hostname.match?(HOSTNAME_PATTERN)

  true
end

#validate_integer(value, name = 'value', min: nil, max: nil) ⇒ Object

Validate integer parameter



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vmpooler/api/input_validator.rb', line 69

def validate_integer(value, name = 'value', min: nil, max: nil)
  return error_response("#{name} is required") if value.nil?

  value_str = value.to_s
  return error_response("#{name} must be a valid integer") unless value_str.match?(INTEGER_PATTERN)

  int_value = value.to_i
  return error_response("#{name} must be at least #{min}") if min && int_value < min
  return error_response("#{name} must be at most #{max}") if max && int_value > max

  int_value
end

#validate_lifetime(lifetime) ⇒ Object

Validate lifetime (TTL) in hours



99
100
101
102
103
104
# File 'lib/vmpooler/api/input_validator.rb', line 99

def validate_lifetime(lifetime)
  validated = validate_integer(lifetime, 'Lifetime', min: 1, max: 168) # max 1 week
  return validated if validated.is_a?(Hash) # error response

  validated
end

#validate_pool_name(pool_name) ⇒ Object

Validate pool/template name



34
35
36
37
38
39
40
# File 'lib/vmpooler/api/input_validator.rb', line 34

def validate_pool_name(pool_name)
  return error_response('Pool name is required') if pool_name.nil? || pool_name.empty?
  return error_response('Pool name too long') if pool_name.length > MAX_POOL_NAME_LENGTH
  return error_response('Invalid pool name format') unless pool_name.match?(POOL_NAME_PATTERN)

  true
end

#validate_reason(reason) ⇒ Object

Validate reason text



107
108
109
110
111
112
113
114
115
116
# File 'lib/vmpooler/api/input_validator.rb', line 107

def validate_reason(reason)
  return true if reason.nil? || reason.empty?
  return error_response('Reason too long') if reason.length > MAX_REASON_LENGTH

  # Sanitize to prevent XSS/injection
  sanitized = reason.gsub(/[<>"']/, '')
  return error_response('Reason contains invalid characters') if sanitized != reason

  true
end

#validate_tag(key, value) ⇒ Object

Validate tag key and value



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/vmpooler/api/input_validator.rb', line 43

def validate_tag(key, value)
  return error_response('Tag key is required') if key.nil? || key.empty?
  return error_response('Tag key too long') if key.length > MAX_TAG_KEY_LENGTH
  return error_response('Invalid tag key format') unless key.match?(TAG_KEY_PATTERN)

  if value
    return error_response('Tag value too long') if value.length > MAX_TAG_VALUE_LENGTH

    # Sanitize value to prevent injection attacks
    sanitized_value = value.gsub(/[^\w\s\-.@:\/]/, '')
    return error_response('Tag value contains invalid characters') if sanitized_value != value
  end

  true
end

#validate_token_format(token) ⇒ Object

Validate token format



60
61
62
63
64
65
66
# File 'lib/vmpooler/api/input_validator.rb', line 60

def validate_token_format(token)
  return error_response('Token is required') if token.nil? || token.empty?
  return error_response('Token too long') if token.length > MAX_TOKEN_LENGTH
  return error_response('Invalid token format') unless token.match?(TOKEN_PATTERN)

  true
end

#validate_vm_count(count) ⇒ Object

Validate VM request count



83
84
85
86
87
88
# File 'lib/vmpooler/api/input_validator.rb', line 83

def validate_vm_count(count)
  validated = validate_integer(count, 'VM count', min: 1, max: 100)
  return validated if validated.is_a?(Hash) # error response

  validated
end

#validation_error?(result) ⇒ Boolean

Check if validation result is an error

Returns:

  • (Boolean)


137
138
139
# File 'lib/vmpooler/api/input_validator.rb', line 137

def validation_error?(result)
  result.is_a?(Hash) && result['ok'] == false
end