/lib/gitlab/config/entry/validators.rb

https://gitlab.com/jamgregory/gitlab-ce · Ruby · 316 lines · 251 code · 64 blank · 1 comment · 26 complexity · 966146322c7fb2986b2ef7f5f7bb17ff MD5 · raw file

  1. # frozen_string_literal: true
  2. module Gitlab
  3. module Config
  4. module Entry
  5. module Validators
  6. class AllowedKeysValidator < ActiveModel::EachValidator
  7. def validate_each(record, attribute, value)
  8. unknown_keys = value.try(:keys).to_a - options[:in]
  9. if unknown_keys.any?
  10. record.errors.add(attribute, "contains unknown keys: " +
  11. unknown_keys.join(', '))
  12. end
  13. end
  14. end
  15. class DisallowedKeysValidator < ActiveModel::EachValidator
  16. def validate_each(record, attribute, value)
  17. present_keys = value.try(:keys).to_a & options[:in]
  18. if present_keys.any?
  19. record.errors.add(attribute, "contains disallowed keys: " +
  20. present_keys.join(', '))
  21. end
  22. end
  23. end
  24. class AllowedValuesValidator < ActiveModel::EachValidator
  25. def validate_each(record, attribute, value)
  26. unless options[:in].include?(value.to_s)
  27. record.errors.add(attribute, "unknown value: #{value}")
  28. end
  29. end
  30. end
  31. class AllowedArrayValuesValidator < ActiveModel::EachValidator
  32. def validate_each(record, attribute, value)
  33. unknown_values = value - options[:in]
  34. unless unknown_values.empty?
  35. record.errors.add(attribute, "contains unknown values: " +
  36. unknown_values.join(', '))
  37. end
  38. end
  39. end
  40. class ArrayOfStringsValidator < ActiveModel::EachValidator
  41. include LegacyValidationHelpers
  42. def validate_each(record, attribute, value)
  43. unless validate_array_of_strings(value)
  44. record.errors.add(attribute, 'should be an array of strings')
  45. end
  46. end
  47. end
  48. class ArrayOrStringValidator < ActiveModel::EachValidator
  49. def validate_each(record, attribute, value)
  50. unless value.is_a?(Array) || value.is_a?(String)
  51. record.errors.add(attribute, 'should be an array or a string')
  52. end
  53. end
  54. end
  55. class BooleanValidator < ActiveModel::EachValidator
  56. include LegacyValidationHelpers
  57. def validate_each(record, attribute, value)
  58. unless validate_boolean(value)
  59. record.errors.add(attribute, 'should be a boolean value')
  60. end
  61. end
  62. end
  63. class DurationValidator < ActiveModel::EachValidator
  64. include LegacyValidationHelpers
  65. def validate_each(record, attribute, value)
  66. unless validate_duration(value)
  67. record.errors.add(attribute, 'should be a duration')
  68. end
  69. if options[:limit]
  70. unless validate_duration_limit(value, options[:limit])
  71. record.errors.add(attribute, 'should not exceed the limit')
  72. end
  73. end
  74. end
  75. end
  76. class HashOrStringValidator < ActiveModel::EachValidator
  77. def validate_each(record, attribute, value)
  78. unless value.is_a?(Hash) || value.is_a?(String)
  79. record.errors.add(attribute, 'should be a hash or a string')
  80. end
  81. end
  82. end
  83. class HashOrIntegerValidator < ActiveModel::EachValidator
  84. def validate_each(record, attribute, value)
  85. unless value.is_a?(Hash) || value.is_a?(Integer)
  86. record.errors.add(attribute, 'should be a hash or an integer')
  87. end
  88. end
  89. end
  90. class KeyValidator < ActiveModel::EachValidator
  91. include LegacyValidationHelpers
  92. def validate_each(record, attribute, value)
  93. if validate_string(value)
  94. validate_path(record, attribute, value)
  95. else
  96. record.errors.add(attribute, 'should be a string or symbol')
  97. end
  98. end
  99. private
  100. def validate_path(record, attribute, value)
  101. path = CGI.unescape(value.to_s)
  102. if path.include?('/')
  103. record.errors.add(attribute, 'cannot contain the "/" character')
  104. elsif path == '.' || path == '..'
  105. record.errors.add(attribute, 'cannot be "." or ".."')
  106. end
  107. end
  108. end
  109. class RegexpValidator < ActiveModel::EachValidator
  110. include LegacyValidationHelpers
  111. def validate_each(record, attribute, value)
  112. unless validate_regexp(value)
  113. record.errors.add(attribute, 'must be a regular expression')
  114. end
  115. end
  116. protected
  117. def fallback
  118. false
  119. end
  120. private
  121. def matches_syntax?(value)
  122. Gitlab::UntrustedRegexp::RubySyntax.matches_syntax?(value)
  123. end
  124. def validate_regexp(value)
  125. matches_syntax?(value) &&
  126. Gitlab::UntrustedRegexp::RubySyntax.valid?(value, fallback: fallback)
  127. end
  128. end
  129. class ArrayOfStringsOrRegexpsValidator < RegexpValidator
  130. def validate_each(record, attribute, value)
  131. unless validate_array_of_strings_or_regexps(value)
  132. record.errors.add(attribute, 'should be an array of strings or regexps')
  133. end
  134. end
  135. private
  136. def validate_array_of_strings_or_regexps(values)
  137. values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp))
  138. end
  139. def validate_string_or_regexp(value)
  140. return false unless value.is_a?(String)
  141. return validate_regexp(value) if matches_syntax?(value)
  142. true
  143. end
  144. end
  145. class ArrayOfStringsOrRegexpsWithFallbackValidator < ArrayOfStringsOrRegexpsValidator
  146. protected
  147. def fallback
  148. true
  149. end
  150. end
  151. class ArrayOfStringsOrStringValidator < RegexpValidator
  152. def validate_each(record, attribute, value)
  153. unless validate_array_of_strings_or_string(value)
  154. record.errors.add(attribute, 'should be an array of strings or a string')
  155. end
  156. end
  157. private
  158. def validate_array_of_strings_or_string(values)
  159. validate_array_of_strings(values) || validate_string(values)
  160. end
  161. end
  162. class TypeValidator < ActiveModel::EachValidator
  163. def validate_each(record, attribute, value)
  164. type = options[:with]
  165. raise unless type.is_a?(Class)
  166. unless value.is_a?(type)
  167. message = options[:message] || "should be a #{type.name}"
  168. record.errors.add(attribute, message)
  169. end
  170. end
  171. end
  172. class VariablesValidator < ActiveModel::EachValidator
  173. include LegacyValidationHelpers
  174. def validate_each(record, attribute, value)
  175. unless validate_variables(value)
  176. record.errors.add(attribute, 'should be a hash of key value pairs')
  177. end
  178. end
  179. end
  180. class PortNamePresentAndUniqueValidator < ActiveModel::EachValidator
  181. def validate_each(record, attribute, value)
  182. return unless value.is_a?(Array)
  183. ports_size = value.count
  184. return if ports_size <= 1
  185. named_ports = value.select { |e| e.is_a?(Hash) }.map { |e| e[:name] }.compact.map(&:downcase)
  186. if ports_size != named_ports.size
  187. record.errors.add(attribute, 'when there is more than one port, a unique name should be added')
  188. end
  189. if ports_size != named_ports.uniq.size
  190. record.errors.add(attribute, 'each port name must be different')
  191. end
  192. end
  193. end
  194. class PortUniqueValidator < ActiveModel::EachValidator
  195. def validate_each(record, attribute, value)
  196. value = ports(value)
  197. return unless value.is_a?(Array)
  198. ports_size = value.count
  199. return if ports_size <= 1
  200. if transform_ports(value).size != ports_size
  201. record.errors.add(attribute, 'each port number can only be referenced once')
  202. end
  203. end
  204. private
  205. def ports(current_data)
  206. current_data
  207. end
  208. def transform_ports(raw_ports)
  209. raw_ports.map do |port|
  210. case port
  211. when Integer
  212. port
  213. when Hash
  214. port[:number]
  215. end
  216. end.uniq
  217. end
  218. end
  219. class JobPortUniqueValidator < PortUniqueValidator
  220. private
  221. def ports(current_data)
  222. return unless current_data.is_a?(Hash)
  223. (image_ports(current_data) + services_ports(current_data)).compact
  224. end
  225. def image_ports(current_data)
  226. return [] unless current_data[:image].is_a?(Hash)
  227. current_data.dig(:image, :ports).to_a
  228. end
  229. def services_ports(current_data)
  230. current_data.dig(:services).to_a.flat_map { |service| service.is_a?(Hash) ? service[:ports] : nil }
  231. end
  232. end
  233. class ServicesWithPortsAliasUniqueValidator < ActiveModel::EachValidator
  234. def validate_each(record, attribute, value)
  235. current_aliases = aliases(value)
  236. return if current_aliases.empty?
  237. unless aliases_unique?(current_aliases)
  238. record.errors.add(:config, 'alias must be unique in services with ports')
  239. end
  240. end
  241. private
  242. def aliases(value)
  243. value.select { |s| s.is_a?(Hash) && s[:ports] }.pluck(:alias) # rubocop:disable CodeReuse/ActiveRecord
  244. end
  245. def aliases_unique?(aliases)
  246. aliases.size == aliases.uniq.size
  247. end
  248. end
  249. end
  250. end
  251. end
  252. end