Class: Parse::Webhooks::Payload

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Serializers::JSON
Defined in:
lib/parse/webhooks/payload.rb

Overview

Represents the data structure that Parse server sends to a registered webhook. Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The Parse::Webhooks class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are Payload type of objects that represent that data that Parse sends webhook handlers.

Constant Summary collapse

ATTRIBUTES =

The set of keys that can be contained in a Parse hash payload for a webhook.

{ master: nil, user: nil,
installationId: nil, params: nil,
functionName: nil, object: nil,
original: nil, update: nil,
query: nil, log: nil,
objects: nil,
triggerName: nil }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash = {}) ⇒ Payload

You would normally never create a Parse::Webhooks::Payload object since it is automatically provided to you when using Parse::Webhooks.

See Also:



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
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/parse/webhooks/payload.rb', line 88

def initialize(hash = {})
  hash = JSON.parse(hash, max_nesting: 20) if hash.is_a?(String)
  hash = Hash[hash.map { |k, v| [k.to_s.underscore.to_sym, v] }]
  @raw = hash
  @master = hash[:master]
  # Capture the caller's session token from the *unscrubbed* user hash
  # before scrub_credentials strips it below. Parse Server includes
  # `user.sessionToken` on every trigger fired by a logged-in caller
  # (it is absent for master-key-originated requests). Pulling it aside
  # here -- rather than leaving it in @user -- keeps it out of any object
  # a handler might persist and out of #as_json / the request log, while
  # still letting a handler opt in to acting as the calling user via
  # #session_token / #user_client / #user_agent.
  @session_token = self.class.extract_session_token(hash[:user])
  # Webhook trigger payloads (beforeSave/afterSave/etc.) are delivered by
  # Parse Server and, when a webhook key is configured (the default; see
  # Parse::Webhooks.allow_unauthenticated for the opt-out used in tests /
  # local dev), authenticated by it -- so they are treated as trusted,
  # server-authoritative state. A handler is meant to receive the full
  # object -- createdAt/updatedAt, ACL, internal fields and all. The only
  # thing stripped here is genuine credential material a handler never
  # legitimately needs to read inline (live session tokens -- captured
  # above for opt-in user-scoped clients first -- and offline-crackable
  # password hashes); see WEBHOOK_TRIGGER_CREDENTIAL_KEYS. Protection
  # against *persisting* forged privileged fields lives on the write path
  # (changes_payload emits only declared, dirty-tracked properties), not on
  # this read path.
  if hash[:user].present?
    # Trusted hydration via .build (not .new) so server-sent timestamps and
    # data fields remain readable; credentials are removed first. Note
    # Parse::User applies its own protections, so `payload.user.auth_data`
    # is not exposed here. The built object is pristine, so a handler that
    # saves payload.user transmits nothing (no dirty changes) and cannot
    # persist forgeries.
    @user = Parse::User.build(self.class.scrub_credentials(hash[:user]))
  end
  @installation_id = hash[:installation_id]
  @params = hash[:params]
  @params = @params.with_indifferent_access if @params.is_a?(Hash)
  @function_name = hash[:function_name]
  @object = self.class.scrub_credentials(hash[:object])
  @trigger_name = hash[:trigger_name]
  @original = self.class.scrub_credentials(hash[:original])
  @update = self.class.scrub_credentials(hash[:update]) || {}
  # Added for beforeFind and afterFind triggers
  @query = hash[:query]
  @objects = hash[:objects] || []
  @log = hash[:log]
end

Instance Attribute Details

#function_nameString Also known as: functionName

Returns the name of the function.

Returns:

  • (String)

    the name of the function.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#installation_idString Also known as: installationId

Returns The identifier of the device that submitted the request.

Returns:

  • (String)

    The identifier of the device that submitted the request.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#logObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#masterBoolean

Returns whether the master key was used for this request.

Returns:

  • (Boolean)

    whether the master key was used for this request.



64
65
66
# File 'lib/parse/webhooks/payload.rb', line 64

def master
  @master
end

#objectHash

In a beforeSave, this attribute is the final object that will be persisted.

Returns:

  • (Hash)

    the object hash related to a webhook trigger request.

See Also:



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#objectsObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#originalHash

In a beforeSave, for previously saved objects, this attribute is the Parse::Object that was previously in the persistent store.

Returns:

  • (Hash)

    the object hash related to a webhook trigger request.

See Also:



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#paramsHash

Returns The list of function arguments submitted for a function request.

Returns:

  • (Hash)

    The list of function arguments submitted for a function request.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#queryObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#rawHash

Returns the raw payload from Parse server.

Returns:

  • (Hash)

    the raw payload from Parse server.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#session_tokenObject (readonly)

Returns the value of attribute session_token.



78
79
80
# File 'lib/parse/webhooks/payload.rb', line 78

def session_token
  @session_token
end

#trigger_nameString Also known as: triggerName

Returns the name of the trigger (ex. beforeSave, afterSave, etc.).

Returns:

  • (String)

    the name of the trigger (ex. beforeSave, afterSave, etc.)



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#updateHash

Returns the update payload in the request.

Returns:

  • (Hash)

    the update payload in the request.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#userParse::User

Returns the user who performed this request or action.

Returns:

  • (Parse::User)

    the user who performed this request or action.



64
# File 'lib/parse/webhooks/payload.rb', line 64

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

Instance Method Details

#after_delete?Boolean

true if this is a afterDelete webhook trigger request.

Returns:

  • (Boolean)


314
315
316
# File 'lib/parse/webhooks/payload.rb', line 314

def after_delete?
  trigger? && @trigger_name.to_sym == :afterDelete
end

#after_find?Boolean

true if this is a afterFind webhook trigger request.

Returns:

  • (Boolean)


324
325
326
# File 'lib/parse/webhooks/payload.rb', line 324

def after_find?
  trigger? && @trigger_name.to_sym == :afterFind
end

#after_save?Boolean

true if this is a afterSave webhook trigger request.

Returns:

  • (Boolean)


304
305
306
# File 'lib/parse/webhooks/payload.rb', line 304

def after_save?
  trigger? && @trigger_name.to_sym == :afterSave
end

#after_trigger?Boolean

true if this is a afterSave or afterDelete webhook trigger request.

Returns:

  • (Boolean)


294
295
296
# File 'lib/parse/webhooks/payload.rb', line 294

def after_trigger?
  after_save? || after_delete? || after_find?
end

#attributesATTRIBUTES

Returns:



184
185
186
# File 'lib/parse/webhooks/payload.rb', line 184

def attributes
  ATTRIBUTES
end

#before_delete?Boolean

true if this is a beforeDelete webhook trigger request.

Returns:

  • (Boolean)


309
310
311
# File 'lib/parse/webhooks/payload.rb', line 309

def before_delete?
  trigger? && @trigger_name.to_sym == :beforeDelete
end

#before_find?Boolean

true if this is a beforeFind webhook trigger request.

Returns:

  • (Boolean)


319
320
321
# File 'lib/parse/webhooks/payload.rb', line 319

def before_find?
  trigger? && @trigger_name.to_sym == :beforeFind
end

#before_save?Boolean

true if this is a beforeSave webhook trigger request.

Returns:

  • (Boolean)


299
300
301
# File 'lib/parse/webhooks/payload.rb', line 299

def before_save?
  trigger? && @trigger_name.to_sym == :beforeSave
end

#before_trigger?Boolean

true if this is a beforeSave or beforeDelete webhook trigger request.

Returns:

  • (Boolean)


289
290
291
# File 'lib/parse/webhooks/payload.rb', line 289

def before_trigger?
  before_save? || before_delete? || before_find?
end

#client_initiated?Boolean

Returns true if this webhook was triggered by a client request (JavaScript, iOS, Android, etc.) This is the inverse of ruby_initiated? and is useful for callback logic that should only run for client-initiated operations.

Returns:

  • (Boolean)

    true if the request originated from a client (not Ruby)



527
528
529
# File 'lib/parse/webhooks/payload.rb', line 527

def client_initiated?
  !ruby_initiated?
end

#error!(msg = "") ⇒ Parse::Webhooks::ResponseError

This method will intentionally raise a ResponseError with a specific message. When used inside of a registered cloud code webhook function or trigger, will halt processing and return the proper error response code back to the Parse server.

Parameters:

  • msg (String) (defaults to: "")

    the error message to send back.

Returns:

Raises:

  • Parse::Webhooks::ResponseError



482
483
484
# File 'lib/parse/webhooks/payload.rb', line 482

def error!(msg = "")
  raise Parse::Webhooks::ResponseError, msg
end

#function?Boolean

true if this is a webhook function request.

Returns:

  • (Boolean)


214
215
216
# File 'lib/parse/webhooks/payload.rb', line 214

def function?
  @function_name.present?
end

#inspectObject

Redacted inspection. The default Ruby #inspect would dump every ivar, including the captured @session_token and the pre-scrub @raw hash (which still holds the caller's sessionToken and any password hashes). That is exactly the surface an error reporter or a stray p payload hits, so show only non-sensitive routing fields and a boolean for the token's presence. Use #as_json / the individual accessors for the (already credential-scrubbed) object data.



195
196
197
198
199
200
# File 'lib/parse/webhooks/payload.rb', line 195

def inspect
  "#<#{self.class.name} trigger=#{@trigger_name.inspect} " \
  "function=#{@function_name.inspect} class=#{parse_class.inspect} " \
  "id=#{parse_id.inspect} master=#{@master ? true : false} " \
  "session_token=#{@session_token ? "[FILTERED]" : "nil"}>"
end

#master?Boolean

true if the master key was used for this request.

Returns:

  • (Boolean)


219
220
221
# File 'lib/parse/webhooks/payload.rb', line 219

def master?
  @master.present?
end

#object?Boolean

true if this request is a trigger that contains an object.

Returns:

  • (Boolean)


329
330
331
# File 'lib/parse/webhooks/payload.rb', line 329

def object?
  trigger? && @object.present?
end

#original_parse_objectParse::Object

Returns a Parse::Object from the original object.

Returns:



334
335
336
337
338
339
340
# File 'lib/parse/webhooks/payload.rb', line 334

def original_parse_object
  return nil unless @original.is_a?(Hash)
  # Always pass the trigger's expected class explicitly so the
  # className inside the payload cannot redirect this hydration to a
  # different class.
  Parse::Object.build(@original, parse_class)
end

#parse_classString

Returns the name of the Parse class for this request.

Returns:

  • (String)

    the name of the Parse class for this request.



269
270
271
272
273
# File 'lib/parse/webhooks/payload.rb', line 269

def parse_class
  return @webhook_class if @webhook_class.present?
  return nil unless @object.present?
  @object[Parse::Model::KEY_CLASS_NAME] || @object[:className]
end

#parse_idString Also known as: objectId

Returns the objectId in this request.

Returns:

  • (String)

    the objectId in this request.



276
277
278
279
# File 'lib/parse/webhooks/payload.rb', line 276

def parse_id
  return nil unless @object.present?
  @object[Parse::Model::OBJECT_ID] || @object[:objectId]
end

#parse_object(pristine = false) ⇒ Parse::Object

This method returns a Parse::Object by combining the original object, if was provided, with the final object. This will return a dirty tracked Parse::Object subclass, that will have information on which fields have changed between the previous state in the persistent store and the one about to be saved.

Parameters:

  • pristine (Boolean) (defaults to: false)

    whether the object should be returned without dirty tracking.

Returns:

  • (Parse::Object)

    a dirty tracked Parse::Object subclass instance



348
349
350
351
352
353
354
355
356
357
358
# File 'lib/parse/webhooks/payload.rb', line 348

def parse_object(pristine = false)
  return nil unless object?
  return Parse::Object.build(@object, parse_class) if pristine
  # Memoize so pre-block guard application and the user webhook handler
  # observe the same instance. Otherwise field_guards applied on the
  # framework's pre-built object would be invisible to the block's
  # later parse_object call (which would construct a fresh dirty-tracked
  # object from @object/@original).
  return @parse_object if defined?(@parse_object) && !@parse_object.nil?
  @parse_object = build_parse_object
end

#parse_queryParse::Query

Returns the Parse query for a beforeFind trigger.

Returns:

  • (Parse::Query)

    the Parse query for a beforeFind trigger.



487
488
489
490
# File 'lib/parse/webhooks/payload.rb', line 487

def parse_query
  return nil unless parse_class.present? && @query.is_a?(Hash)
  Parse::Query.new parse_class, @query
end

#ruby_initiated?Boolean

Returns true if this webhook was triggered by a Ruby Parse Stack request. This is determined by checking for the 'RB' prefix in the request ID header. This flag is useful for preventing callback loops and implementing intelligent callback handling based on the request origin.

Returns:

  • (Boolean)

    true if the request originated from Ruby Parse Stack



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/parse/webhooks/payload.rb', line 497

def ruby_initiated?
  @ruby_initiated ||= begin
      request_id = nil

      if @raw.respond_to?(:[])
        # Check for headers at the top level first
        request_id = @raw["x-parse-request-id"] || @raw["X-Parse-Request-Id"] ||
                     @raw[:x_parse_request_id] || @raw[:'X-Parse-Request-Id']

        # If not found at top level, check nested headers
        if request_id.nil?
          headers_sym = @raw[:headers] if @raw[:headers].is_a?(Hash)
          headers_str = @raw["headers"] if @raw["headers"].is_a?(Hash)

          if headers_sym
            request_id = headers_sym["x-parse-request-id"] || headers_sym["X-Parse-Request-Id"]
          elsif headers_str
            request_id = headers_str["x-parse-request-id"] || headers_str["X-Parse-Request-Id"]
          end
        end
      end

      request_id&.start_with?("_RB_") || false
    end
end

#session_token?Boolean

true if this payload carried a caller session token -- i.e. the originating request was made by a logged-in user rather than the master key, so #user_client / #user_agent can act as that user.

Returns:

  • (Boolean)


227
228
229
# File 'lib/parse/webhooks/payload.rb', line 227

def session_token?
  !@session_token.nil?
end

#trigger?Boolean

true if this is a webhook trigger request.

Returns:

  • (Boolean)


284
285
286
# File 'lib/parse/webhooks/payload.rb', line 284

def trigger?
  @trigger_name.present?
end

#user_agent(**opts) ⇒ Parse::Agent?

An opt-in, non-master Agent scoped to the webhook caller's session token. Because its client has no master key and it is built with a non-empty +session_token:+, the agent runs in CLIENT MODE: every tool/query routes through a path Parse Server (or the SDK's own ACL/CLP enforcement layer) authorizes as the calling user, with no master-key fallback to silently bypass row-level security. This is the handle to use when a handler should read or act strictly within the caller's permissions. Additional agent options (e.g. +permissions: :readwrite+) may be passed through.

Parameters:

Returns:

  • (Parse::Agent, nil)

    +nil+ when the payload carried no token.



257
258
259
260
261
262
263
264
265
266
# File 'lib/parse/webhooks/payload.rb', line 257

def user_agent(**opts)
  return nil if @session_token.nil?
  require_relative "../agent" unless defined?(Parse::Agent)
  # Strip the two identity kwargs from the passthrough: a Ruby double-splat
  # that repeats an explicit keyword WINS, so user_agent(client: master)
  # or user_agent(session_token: other) would otherwise silently defeat the
  # whole point (scoping to the caller). The scoping is non-negotiable here.
  opts = opts.except(:session_token, :client)
  Parse::Agent.new(session_token: @session_token, client: user_client, **opts)
end

#user_clientParse::Client?

An opt-in, user-scoped Client for acting on the server as the webhook's calling user. It mirrors the default client's connection settings (+server_url+, +application_id+, +api_key+) but carries NO master key and BINDS the caller's #session_token, so every request it makes -- with no further ceremony -- is authorized by Parse Server as that user: ACL, CLP and +protectedFields+ are all enforced. (A Parse.with_session block still overrides the bound token if you need to act as someone else within a call.) Memoized per payload, since each webhook delivery carries a distinct token.

Returns:

  • (Parse::Client, nil)

    +nil+ when the payload carried no token.



241
242
243
244
# File 'lib/parse/webhooks/payload.rb', line 241

def user_client
  return nil if @session_token.nil?
  @user_client ||= Parse::Client.client.become(@session_token)
end

#wlog(s) ⇒ Object

Method to print to standard that utilizes the an internal id to make it easier to trace incoming requests.



204
205
206
207
208
209
210
211
# File 'lib/parse/webhooks/payload.rb', line 204

def wlog(s)
  # generates a unique random number in order to be used in logging. This
  # is useful when debugging issues in production where one server instance
  # may be running multiple threads and you want to trace the incoming call.
  @rid ||= rand(999).to_s.rjust(3)
  puts "[> #{@rid}] #{s}"
  @rid
end