diff options
Diffstat (limited to 'app/validators')
3 files changed, 203 insertions, 18 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 diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json index 995f2ad6616..8fde92d6312 100644 --- a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json +++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json @@ -52,67 +52,126 @@ { "name": "brakeman", "label": "Brakeman", - "enabled" : true + "enabled" : true, + "description": "Ruby on Rails", + "variables": [ + { + "field" : "SAST_BRAKEMAN_LEVEL", + "label" : "Brakeman confidence level.", + "type": "string", + "default_value": "1", + "value": "", + "size": "SMALL", + "description": "Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low, 2=Medium, 3=High." + } + ] }, { "name": "bandit", "label": "Bandit", - "enabled" : true + "enabled" : true, + "description": "Python", + "variables": [ + { + "field" : "SAST_BANDIT_EXCLUDED_PATHS", + "label" : "Paths to exclude from scan.", + "type": "string", + "default_value": "", + "value": "", + "size": "SMALL", + "description": "Comma-separated list of paths to exclude from scan. Uses Python’s 'fnmatch' syntax; For example: '*/tests/*, */venv/*'" + } + ] }, { "name": "eslint", "label": "ESLint", - "enabled" : true + "enabled" : true, + "description": "JavaScript, TypeScript, React", + "variables": [] }, { "name": "flawfinder", "label": "Flawfinder", - "enabled" : true + "enabled" : true, + "description": "C, C++", + "variables": [ + { + "field" : "SAST_FLAWFINDER_LEVEL", + "label" : "Flawfinder risk level", + "type": "string", + "default_value": "1", + "value": "", + "size": "SMALL", + "description": "Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk." + } + ] }, { "name": "kubesec", "label": "kubesec", - "enabled" : true + "enabled" : true, + "description": "Kubernetes manifests, Helm Charts", + "variables": [] }, { - "name": "nodejsscan", + "name": "nodejs-scan", "label": "Node.js Scan", - "enabled" : true + "enabled" : true, + "description": "Node.js", + "variables": [] }, { "name": "gosec", "label": "Golang Security Checker", - "enabled" : true + "enabled" : true, + "description": "Go", + "variables": [ + { + "field" : "SAST_GOSEC_LEVEL", + "label" : "Gosec confidence level", + "type": "string", + "default_value": "0", + "value": "", + "size": "SMALL", + "description": "Ignore Gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High." + } + ] }, { "name": "phpcs-security-audit", "label": "PHP Security Audit", - "enabled" : true + "enabled" : true, + "description": "PHP", + "variables": [] }, { "name": "pmd-apex", "label": "PMD APEX", - "enabled" : true + "enabled" : true, + "description": "Apex (Salesforce)", + "variables": [] }, { "name": "security-code-scan", "label": "Security Code Scan", - "enabled" : true + "enabled" : true, + "description": ".NET Core, .NET Framework", + "variables": [] }, { "name": "sobelow", "label": "Sobelow", - "enabled" : true + "enabled" : true, + "description": "Elixir (Phoenix)", + "variables": [] }, { "name": "spotbugs", "label": "Spotbugs", - "enabled" : true - }, - { - "name": "secrets", - "label": "Secrets", - "enabled" : true + "enabled" : true, + "description": "Groovy, Java, Scala", + "variables": [] } ] } |