diff --git a/lib/freedom_patches/rails_security_fixes.rb b/lib/freedom_patches/rails_security_fixes.rb new file mode 100644 index 00000000000..6f2438175c6 --- /dev/null +++ b/lib/freedom_patches/rails_security_fixes.rb @@ -0,0 +1,124 @@ +module HTML + class WhiteListSanitizer + # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute + def sanitize_css(style) + # disallow urls + style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ') + + # gauntlet + if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ || + style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/ + return '' + end + + clean = [] + style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val| + if allowed_css_properties.include?(prop.downcase) + clean << prop + ': ' + val + ';' + elsif shorthand_css_properties.include?(prop.split('-')[0].downcase) + unless val.split().any? do |keyword| + !allowed_css_keywords.include?(keyword) && + keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/ + end + clean << prop + ': ' + val + ';' + end + end + end + clean.join(' ') + end + end +end + +module HTML + class WhiteListSanitizer + self.protocol_separator = /:|(�*58)|(p)|(�*3a)|(%|%)3A/i + + def contains_bad_protocols?(attr_name, value) + uri_attributes.include?(attr_name) && + (value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip)) + end + end +end + +module ActiveRecord + class Relation + + def where_values_hash + equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| + node.left.relation.name == table_name + } + + Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access + end + + end +end + +module ActiveRecord + class PredicateBuilder # :nodoc: + def self.build_from_hash(engine, attributes, default_table, allow_table_name = true) + predicates = attributes.map do |column, value| + table = default_table + + if allow_table_name && value.is_a?(Hash) + table = Arel::Table.new(column, engine) + + if value.empty? + '1 = 2' + else + build_from_hash(engine, value, table, false) + end + else + column = column.to_s + + if allow_table_name && column.include?('.') + table_name, column = column.split('.', 2) + table = Arel::Table.new(table_name, engine) + end + + attribute = table[column] + + case value + when ActiveRecord::Relation + value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty? + attribute.in(value.arel.ast) + when Array, ActiveRecord::Associations::CollectionProxy + values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x} + ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)} + + array_predicates = ranges.map {|range| attribute.in(range)} + + if values.include?(nil) + values = values.compact + if values.empty? + array_predicates << attribute.eq(nil) + else + array_predicates << attribute.in(values.compact).or(attribute.eq(nil)) + end + else + array_predicates << attribute.in(values) + end + + array_predicates.inject {|composite, predicate| composite.or(predicate)} + when Range, Arel::Relation + attribute.in(value) + when ActiveRecord::Base + attribute.eq(value.id) + when Class + # FIXME: I think we need to deprecate this behavior + attribute.eq(value.name) + when Integer, ActiveSupport::Duration + # Arel treats integers as literals, but they should be quoted when compared with strings + column = engine.connection.schema_cache.columns_hash[table.name][attribute.name.to_s] + attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column))) + else + attribute.eq(value) + end + end + end + + predicates.flatten + end + end +end +