summaryrefslogtreecommitdiff
path: root/app/validators
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 21:08:18 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 21:08:18 +0000
commit692f4b734f1976b690dccb5458c198b5205c51b5 (patch)
treec6af56b7127850615b9dc5626cefbe665fd96ea9 /app/validators
parent592223823c8ebf6e32d98e4b12620ba8ff043cca (diff)
downloadgitlab-ce-692f4b734f1976b690dccb5458c198b5205c51b5.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/validators')
-rw-r--r--app/validators/feature_flag_strategies_validator.rb95
-rw-r--r--app/validators/feature_flag_user_xids_validator.rb31
2 files changed, 126 insertions, 0 deletions
diff --git a/app/validators/feature_flag_strategies_validator.rb b/app/validators/feature_flag_strategies_validator.rb
new file mode 100644
index 00000000000..e542d52c50a
--- /dev/null
+++ b/app/validators/feature_flag_strategies_validator.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
+ STRATEGY_DEFAULT = 'default'.freeze
+ STRATEGY_GRADUALROLLOUTUSERID = 'gradualRolloutUserId'.freeze
+ STRATEGY_USERWITHID = 'userWithId'.freeze
+ # Order key names alphabetically
+ STRATEGIES = {
+ STRATEGY_DEFAULT => [].freeze,
+ STRATEGY_GRADUALROLLOUTUSERID => %w[groupId percentage].freeze,
+ STRATEGY_USERWITHID => ['userIds'].freeze
+ }.freeze
+ USERID_MAX_LENGTH = 256
+
+ def validate_each(record, attribute, value)
+ return unless value
+
+ if value.is_a?(Array) && value.all? { |s| s.is_a?(Hash) }
+ value.each do |strategy|
+ strategy_validations(record, attribute, strategy)
+ end
+ else
+ error(record, attribute, 'must be an array of strategy hashes')
+ end
+ end
+
+ private
+
+ def strategy_validations(record, attribute, strategy)
+ validate_name(record, attribute, strategy) &&
+ validate_parameters_type(record, attribute, strategy) &&
+ validate_parameters_keys(record, attribute, strategy) &&
+ validate_parameters_values(record, attribute, strategy)
+ end
+
+ def validate_name(record, attribute, strategy)
+ STRATEGIES.key?(strategy['name']) || error(record, attribute, 'strategy name is invalid')
+ end
+
+ def validate_parameters_type(record, attribute, strategy)
+ strategy['parameters'].is_a?(Hash) || error(record, attribute, 'parameters are invalid')
+ end
+
+ def validate_parameters_keys(record, attribute, strategy)
+ name, parameters = strategy.values_at('name', 'parameters')
+ actual_keys = parameters.keys.sort
+ expected_keys = STRATEGIES[name]
+ expected_keys == actual_keys || error(record, attribute, 'parameters are invalid')
+ end
+
+ def validate_parameters_values(record, attribute, strategy)
+ case strategy['name']
+ when STRATEGY_GRADUALROLLOUTUSERID
+ gradual_rollout_user_id_parameters_validation(record, attribute, strategy)
+ when STRATEGY_USERWITHID
+ user_with_id_parameters_validation(record, attribute, strategy)
+ end
+ end
+
+ def gradual_rollout_user_id_parameters_validation(record, attribute, strategy)
+ percentage = strategy.dig('parameters', 'percentage')
+ group_id = strategy.dig('parameters', 'groupId')
+
+ unless percentage.is_a?(String) && percentage.match(/\A[1-9]?[0-9]\z|\A100\z/)
+ error(record, attribute, 'percentage must be a string between 0 and 100 inclusive')
+ end
+
+ unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/)
+ error(record, attribute, 'groupId parameter is invalid')
+ end
+ end
+
+ def user_with_id_parameters_validation(record, attribute, strategy)
+ user_ids = strategy.dig('parameters', 'userIds')
+ unless user_ids.is_a?(String) && !user_ids.match(/[\n\r\t]|,,/) && valid_ids?(user_ids.split(","))
+ error(record, attribute, "userIds must be a string of unique comma separated values each #{USERID_MAX_LENGTH} characters or less")
+ end
+ end
+
+ def valid_ids?(user_ids)
+ user_ids.uniq.length == user_ids.length &&
+ user_ids.all? { |id| valid_id?(id) }
+ end
+
+ def valid_id?(user_id)
+ user_id.present? &&
+ user_id.strip == user_id &&
+ user_id.length <= USERID_MAX_LENGTH
+ end
+
+ def error(record, attribute, msg)
+ record.errors.add(attribute, msg)
+ false
+ end
+end
diff --git a/app/validators/feature_flag_user_xids_validator.rb b/app/validators/feature_flag_user_xids_validator.rb
new file mode 100644
index 00000000000..a840993a94b
--- /dev/null
+++ b/app/validators/feature_flag_user_xids_validator.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class FeatureFlagUserXidsValidator < ActiveModel::EachValidator
+ USERXID_MAX_LENGTH = 256
+
+ def validate_each(record, attribute, value)
+ self.class.validate_user_xids(record, attribute, value, attribute)
+ end
+
+ class << self
+ def validate_user_xids(record, attribute, user_xids, error_message_attribute_name)
+ unless user_xids.is_a?(String) && !user_xids.match(/[\n\r\t]|,,/) && valid_xids?(user_xids.split(","))
+ record.errors.add(attribute,
+ "#{error_message_attribute_name} must be a string of unique comma separated values each #{USERXID_MAX_LENGTH} characters or less")
+ end
+ end
+
+ private
+
+ def valid_xids?(user_xids)
+ user_xids.uniq.length == user_xids.length &&
+ user_xids.all? { |xid| valid_xid?(xid) }
+ end
+
+ def valid_xid?(user_xid)
+ user_xid.present? &&
+ user_xid.strip == user_xid &&
+ user_xid.length <= USERXID_MAX_LENGTH
+ end
+ end
+end