Class: Parse::Model::Builder
- Inherits:
-
Object
- Object
- Parse::Model::Builder
- Defined in:
- lib/parse/model/core/builder.rb
Overview
This class provides a method to automatically generate Parse::Object subclasses, including their properties and inferred associations by importing the schema for the remote collections in a Parse application.
Constant Summary collapse
- VALID_CLASS_NAME =
Regex matching className strings safe to install as a Ruby constant. Server-returned className must satisfy this; otherwise we refuse to touch the global namespace.
/\A_?[A-Za-z][A-Za-z0-9_]{0,127}\z/.freeze
- PROTECTED_SYSTEM_CLASSES =
Parse Server system classes that ship with the SDK as hand-written subclasses (Parse::User, Parse::Role, etc.). Schema-driven builds must NOT install additional fields or associations on these — a compromised Parse Server could otherwise inject an
is_adminproperty onto the realParse::Userclass, or apassword_historyaccessor onto_Session, by returning a poisoned schema. %w[ _User _Role _Session _Installation _Product _Audience _PushStatus _JobStatus _JobSchedule _Hooks _GlobalConfig _SCHEMA _GraphQLConfig _Idempotency _Audit ].freeze
Class Method Summary collapse
-
.build!(schema) ⇒ Array
Builds a ruby Parse::Object subclass with the provided schema information.
Class Method Details
.build!(schema) ⇒ Array
Builds a ruby Parse::Object subclass with the provided schema information.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/parse/model/core/builder.rb', line 58 def self.build!(schema) unless schema.is_a?(Hash) raise ArgumentError, "Schema parameter should be a Parse schema hash object." end schema = schema.with_indifferent_access fields = schema[:fields] || {} className = schema[:className] if className.blank? raise ArgumentError, "No valid className provided for schema hash" end # Strictly validate the server-returned className before any constant # resolution. This blocks schema-poisoning attacks where a malicious # or compromised Parse Server returns a className like "File", # "Kernel", or "../foo" intending to either rebind a Ruby built-in # constant via const_set or trigger arbitrary autoload via const_get. parse_class_name = className.to_parse_class unless parse_class_name.is_a?(String) && parse_class_name =~ VALID_CLASS_NAME raise ArgumentError, "Unsafe className from schema: #{className.inspect}" end # Prefer the registered Parse::Object descendant lookup (never touches # top-level constants). Only fall back to constant lookup within the # Parse::Generated namespace, never on ::Object. klass = Parse::Model.find_class(className) if klass.nil? if Parse::Generated.const_defined?(parse_class_name, false) klass = Parse::Generated.const_get(parse_class_name, false) end end if klass.nil? klass = ::Class.new(Parse::Object) Parse::Generated.const_set(parse_class_name, klass) end unless klass.is_a?(Class) && klass <= Parse::Object raise ArgumentError, "Resolved class #{klass.inspect} for #{className.inspect} is not a Parse::Object subclass" end # Refuse to install schema-derived fields on protected system # classes. The class is still returned (so callers that call # build! purely for the class lookup continue to work) but no # attacker-controlled belongs_to/has_many/property is added. if PROTECTED_SYSTEM_CLASSES.include?(className.to_s) return klass end base_fields = Parse::Properties::BASE.keys class_fields = klass.field_map.values + [:className] fields.each do |field, type| field = field.to_sym key = field.to_s.underscore.to_sym next if base_fields.include?(field) || class_fields.include?(field) data_type = type[:type].downcase.to_sym if data_type == :pointer klass.belongs_to key, as: safe_target_class(type[:targetClass]), field: field elsif data_type == :relation klass.has_many key, through: :relation, as: safe_target_class(type[:targetClass]), field: field else klass.property key, data_type, field: field end class_fields.push(field) end klass end |