ParamValidator

class
Superclass
Object
Included Modules
Strelka::DataUtilities
Extended With
Forwardable
Loggability
Strelka::MethodUtilities

A validator for user parameters.

Usage

require 'strelka/paramvalidator'

validator = Strelka::ParamValidator.new

# Add validation criteria for input parameters
validator.add( :name, /^(?<lastname>\S+), (?<firstname>\S+)$/, "Customer Name" )
validator.add( :email, "Customer Email" )
validator.add( :feedback, :printable, "Customer Feedback" )
validator.override( :email, :printable, "Your Email Address" )

# Untaint all parameter values which match their constraints
validate.untaint_all_constraints = true

# Now pass in tainted values in a hash (e.g., from an HTML form)
validator.validate( req.params )

# Now if there weren't any errors, use some form values to fill out the
# success page template
if validator.okay?
        tmpl = template :success
        tmpl.firstname = validator[:name][:firstname]
        tmpl.lastname  = validator[:name][:lastname]
        tmpl.email      = validator[:email]
        tmpl.feedback  = validator[:feedback]
        return tmpl

# Otherwise fill in the error template with auto-generated error messages
# and return that instead.
else
        tmpl = template :feedback_form
        tmpl.errors = validator.error_messages
        return tmpl
end

Constants

PARAMETER_PATTERN_STRIP_RE

Pattern to use to strip binding operators from parameter patterns so they can be used in the middle of routing Regexps.

PARAMS_HASH_RE

Pattern for countint the number of hash levels in a parameter key

Attributes

constraints[R]

The constraints hash

fields[R]

The Hash of raw field data (if validation has occurred)

Public Class Methods

anchor
new()

Create a new Strelka::ParamValidator object.

# File lib/strelka/paramvalidator.rb, line 565
def initialize
        @constraints = {}
        @fields      = {}
        @untaint_all = false

        self.reset
end

Public Instance Methods

anchor
[]( key )

Index fetch operator; fetch the validated (and possible parsed) value for form field key.

# File lib/strelka/paramvalidator.rb, line 831
def []( key )
        self.validate unless self.validated?
        return @valid[ key.to_sym ]
end
anchor
[]=( key, val )

Index assignment operator; set the validated value for form field key to the specified val.

# File lib/strelka/paramvalidator.rb, line 839
def []=( key, val )
        @parsed_params = nil
        @valid[ key.to_sym ] = val
end
anchor
add( name, *flags )
add( name, constraint, *flags )
add( name, description, *flags )
add( name, constraint, description, *flags )

Add a validation for a parameter with the specified name. The args can include a constraint, a description, and one or more flags.

# File lib/strelka/paramvalidator.rb, line 625
def add( name, *args, &block )
        name = name.to_sym
        constraint = Constraint.for( name, *args, &block )

        # No-op if there's already a parameter with the same name and constraint
        if self.constraints.key?( name )
                return if self.constraints[ name ] == constraint
                raise ArgumentError,
                        "parameter %p is already defined as %s; perhaps you meant to use #override?" %
                                [ name.to_s, self.constraints[name] ]
        end

        self.log.debug "Adding parameter %p: %p" % [ name, constraint ]
        self.constraints[ name ] = constraint

        self.validated = false
end
anchor
apply_constraint( constraint, value )

Apply the specified constraint (a Strelka::ParamValidator::Constraint object) to the given value, and add the field to the appropriate field list based on the result.

# File lib/strelka/paramvalidator.rb, line 754
def apply_constraint( constraint, value )
        if !( value.nil? || value == '' )
                result = constraint.apply( value, self.untaint_all? )

                if !result.nil?
                        self.log.debug "  constraint for %p passed: %p" % [ constraint.name, result ]
                        self[ constraint.name ] = result
                else
                        self.log.debug "  constraint for %p failed" % [ constraint.name ]
                        @invalid[ constraint.name.to_s ] = value
                end
        elsif constraint.required?
                self.log.debug "  missing parameter for %p" % [ constraint.name ]
                @missing << constraint.name.to_s
        end
end
anchor
args?()

Returns true if there were arguments given.

# File lib/strelka/paramvalidator.rb, line 852
def args?
        return !self.fields.empty?
end
Also aliased as: has_args?
anchor
constraint_regexp_for( name )

Fetch the constraint/s that apply to the parameter named name as a Regexp, if possible.

# File lib/strelka/paramvalidator.rb, line 782
def constraint_regexp_for( name )
        self.log.debug "  searching for a constraint for %p" % [ name ]

        # Fetch the constraint's regexp
        constraint = self.constraints[ name.to_sym ] or
                raise NameError, "no such parameter %p" % [ name ]
        raise ScriptError,
                "can't route on a parameter with a %p" % [ constraint.class ] unless
                constraint.respond_to?( :pattern )

        re = constraint.pattern
        self.log.debug "  bounded constraint is: %p" % [ re ]

        # Unbind the pattern from beginning or end of line.
        # :TODO: This is pretty ugly. Find a better way of modifying the regex.
        re_str = re.to_s.
                sub( %r{\(\?[\-mix]+:(.*)\)}, '\1' ).
                gsub( PARAMETER_PATTERN_STRIP_RE, '' )
        self.log.debug "  stripped constraint pattern down to: %p" % [ re_str ]

        return Regexp.new( "(?<#{name}>#{re_str})", re.options )
end
anchor
descriptions()

Hash of field descriptions

# File lib/strelka/paramvalidator.rb, line 694
def descriptions
        return self.constraints.each_with_object({}) do |(field,constraint), hash|
                hash[ field ] = constraint.description
        end
end
anchor
descriptions=( new_descs )

Set field descriptions en masse to new_descs.

# File lib/strelka/paramvalidator.rb, line 702
def descriptions=( new_descs )
        new_descs.each do |name, description|
                raise NameError, "no parameter named #{name}" unless
                        self.constraints.key?( name.to_sym )
                self.constraints[ name.to_sym ].description = description
        end
end
anchor
empty?()

Returns true if there were no arguments given.

# File lib/strelka/paramvalidator.rb, line 846
def empty?
        return self.fields.empty?
end
anchor
error_fields()

Return an array of field names which had some kind of error associated with them.

# File lib/strelka/paramvalidator.rb, line 896
def error_fields
        return self.missing | self.invalid.keys
end
anchor
error_messages( include_unknown=false )

Return an error message for each missing or invalid field; if includeUnknown is true, also include messages for unknown fields.

# File lib/strelka/paramvalidator.rb, line 903
def error_messages( include_unknown=false )
        msgs = []

        msgs += self.missing_param_errors + self.invalid_param_errors
        msgs += self.unknown_param_errors if include_unknown

        return msgs
end
anchor
errors?()

Returns true if any fields are missing or contain invalid values.

# File lib/strelka/paramvalidator.rb, line 881
def errors?
        return !self.okay?
end
Also aliased as: has_errors?
anchor
get_description( field )

Get the description for the specified field.

# File lib/strelka/paramvalidator.rb, line 712
def get_description( field )
        constraint = self.constraints[ field.to_sym ] or return nil
        return constraint.description
end
anchor
has_args?()
Alias for: args?
anchor
has_errors?()
Alias for: errors?
anchor
initialize_copy( original )

Copy constructor.

# File lib/strelka/paramvalidator.rb, line 575
def initialize_copy( original )
        fields       = deep_copy( original.fields )
        self.reset
        @fields      = fields
        @constraints = deep_copy( original.constraints )
end
anchor
inspect()

Return a human-readable representation of the validator, suitable for debugging.

# File lib/strelka/paramvalidator.rb, line 677
def inspect
        required, optional = self.constraints.partition do |_, constraint|
                constraint.required?
        end

        return "#<%p:0x%016x %s, profile: [required: %s, optional: %s] global untaint: %s>" % [
                self.class,
                self.object_id / 2,
                self.to_s,
                required.empty? ? "(none)" : required.map( &:last ).map( &:name ).join(','),
                optional.empty? ? "(none)" : optional.map( &:last ).map( &:name ).join(','),
                self.untaint_all? ? "enabled" : "disabled",
        ]
end
anchor
invalid()

The Hash of fields that were present, but invalid (didn't match the field's constraint)

# File lib/strelka/paramvalidator.rb, line 866
def invalid
        self.validate unless self.validated?
        return @invalid
end
anchor
invalid_param_errors()

Return an Array of error messages, one for each field that was invalid from the last validation.

# File lib/strelka/paramvalidator.rb, line 925
def invalid_param_errors
        return self.invalid.collect do |field, _|
                constraint = self.constraints[ field.to_sym ] or
                        raise NameError, "no such field %p!" % [ field ]
                "Invalid value for '%s'" % [ constraint.description ]
        end
end
anchor
merge( params )

Return a new ParamValidator with the additional params merged into its values and re-validated.

# File lib/strelka/paramvalidator.rb, line 946
def merge( params )
        copy = self.dup
        copy.merge!( params )
        return copy
end
anchor
merge!( params )

Merge the specified params into the receiving ParamValidator and re-validate the resulting values.

# File lib/strelka/paramvalidator.rb, line 955
def merge!( params )
        return if params.empty?
        self.log.debug "Merging parameters for revalidation: %p" % [ params ]
        self.revalidate( params )
end
anchor
missing()

The names of fields that were required, but missing from the parameter list.

# File lib/strelka/paramvalidator.rb, line 859
def missing
        self.validate unless self.validated?
        return @missing
end
anchor
missing_param_errors()

Return an Array of error messages, one for each field missing from the last validation.

# File lib/strelka/paramvalidator.rb, line 914
def missing_param_errors
        return self.missing.collect do |field|
                constraint = self.constraints[ field.to_sym ] or
                        raise NameError, "no such field %p!" % [ field ]
                "Missing value for '%s'" % [ constraint.description ]
        end
end
anchor
okay?()

Return true if all required fields were present and all present fields validated correctly.

# File lib/strelka/paramvalidator.rb, line 889
def okay?
        return (self.missing.empty? && self.invalid.empty?)
end
anchor
override( name, *args, &block )

Replace the existing parameter with the specified name. The args replace the existing description, constraints, and flags. See add for details.

# File lib/strelka/paramvalidator.rb, line 646
def override( name, *args, &block )
        name = name.to_sym
        raise ArgumentError,
                "no parameter %p defined; perhaps you meant to use #add?" % [ name.to_s ] unless
                self.constraints.key?( name )

        self.log.debug "Overriding parameter %p" % [ name ]
        self.constraints[ name ] = Constraint.for( name, *args, &block )

        self.validated = false
end
anchor
param_names()

Return the Array of parameter names the validator knows how to validate (as Strings).

# File lib/strelka/paramvalidator.rb, line 660
def param_names
        return self.constraints.keys.map( &:to_s ).sort
end
anchor
reset()

Reset the validation state.

# File lib/strelka/paramvalidator.rb, line 606
def reset
        self.log.debug "Resetting validation state."
        @validated     = false
        @valid         = {}
        @parsed_params = nil
        @missing       = []
        @unknown       = []
        @invalid       = {}
end
anchor
revalidate( params={} )

Clear existing validation information, merge the specified params with any existing raw fields, and re-run the validation.

# File lib/strelka/paramvalidator.rb, line 774
def revalidate( params={} )
        merged_fields = self.fields.merge( params )
        self.reset
        self.validate( merged_fields )
end
anchor
to_s()

Stringified description of the validator

# File lib/strelka/paramvalidator.rb, line 666
def to_s
    "%d parameters (%d valid, %d invalid, %d missing)" % [
        self.fields.size,
        self.valid.size,
        self.invalid.size,
        self.missing.size,
    ]
end
anchor
unknown()

The names of fields that were present in the parameters, but didn't have a corresponding constraint.

# File lib/strelka/paramvalidator.rb, line 874
def unknown
        self.validate unless self.validated?
        return @unknown
end
anchor
unknown_param_errors()

Return an Array of error messages, one for each field present in the parameters in the last validation that didn't have a constraint associated with it.

# File lib/strelka/paramvalidator.rb, line 936
def unknown_param_errors
        self.log.debug "Fetching unknown param errors for %p." % [ self.unknown ]
        return self.unknown.collect do |field|
                "Unknown parameter '%s'" % [ field.capitalize ]
        end
end
anchor
untaint_all?()

Global untainting flag

# File lib/strelka/paramvalidator.rb, line 595
attr_predicate_accessor :untaint_all?
Also aliased as: untaint_all_constraints?
anchor
untaint_all_constraints?()
Alias for: untaint_all?
anchor
valid()

Returns the valid fields after expanding Rails-style 'customer[street]' variables into multi-level hashes.

# File lib/strelka/paramvalidator.rb, line 808
def valid
        self.validate unless self.validated?

        self.log.debug "Building valid fields hash from raw data: %p" % [ @valid ]
        unless @parsed_params
                @parsed_params = {}
                for key, value in @valid
                        self.log.debug "  adding %s: %p" % [ key, value ]
                        value = [ value ] if key.to_s.end_with?( '[]' )
                        if key.to_s.include?( '[' )
                                build_deep_hash( value, @parsed_params, get_levels(key.to_s) )
                        else
                                @parsed_params[ key ] = value
                        end
                end
        end

        return @parsed_params
end
anchor
validate( params=nil, additional_constraints=nil )

Validate the input in params. If the optional additional_constraints is given, merge it with the validator's existing constraints before validating.

# File lib/strelka/paramvalidator.rb, line 720
def validate( params=nil, additional_constraints=nil )
        self.log.debug "Validating."
        self.reset

        # :TODO: Handle the additional_constraints

        params ||= @fields
        params = stringify_keys( params )
        @fields = deep_copy( params )

        self.log.debug "Starting validation with fields: %p" % [ @fields ]

        # Use the constraints list to extract all the parameters that have corresponding
        # constraints
        self.constraints.each do |field, constraint|
                self.log.debug "  applying %s to any %p parameter/s" % [ constraint, field ]
                value = params.delete( field.to_s )
                self.log.debug "  value is: %p" % [ value ]
                self.apply_constraint( constraint, value )
        end

        # Any left over are unknown
        params.keys.each do |field|
                self.log.debug "  unknown field %p" % [ field ]
                @unknown << field
        end

        @validated = true
end
anchor
validated?()

Returns true if the paramvalidator has been given parameters to validate. Adding or overriding constraints resets this.

# File lib/strelka/paramvalidator.rb, line 602
attr_predicate_accessor :validated?
anchor
values_at( *selector )

Returns an array containing valid parameters in the validator corresponding to the given selector(s).

# File lib/strelka/paramvalidator.rb, line 964
def values_at( *selector )
        selector.map!( &:to_sym )
        return self.valid.values_at( *selector )
end