diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-03 21:08:18 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-03 21:08:18 +0000 |
commit | 692f4b734f1976b690dccb5458c198b5205c51b5 (patch) | |
tree | c6af56b7127850615b9dc5626cefbe665fd96ea9 /app/validators | |
parent | 592223823c8ebf6e32d98e4b12620ba8ff043cca (diff) | |
download | gitlab-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.rb | 95 | ||||
-rw-r--r-- | app/validators/feature_flag_user_xids_validator.rb | 31 |
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 |