Class: Parse::Webhooks::Payload
- Inherits:
-
Object
- Object
- Parse::Webhooks::Payload
- 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
-
#function_name ⇒ String
(also: #functionName)
The name of the function.
-
#installation_id ⇒ String
(also: #installationId)
The identifier of the device that submitted the request.
-
#log ⇒ Object
The query request in a beforeFind trigger.
-
#master ⇒ Boolean
Whether the master key was used for this request.
-
#object ⇒ Hash
In a beforeSave, this attribute is the final object that will be persisted.
-
#objects ⇒ Object
The query request in a beforeFind trigger.
-
#original ⇒ Hash
In a beforeSave, for previously saved objects, this attribute is the Parse::Object that was previously in the persistent store.
-
#params ⇒ Hash
The list of function arguments submitted for a function request.
-
#query ⇒ Object
The query request in a beforeFind trigger.
-
#raw ⇒ Hash
The raw payload from Parse server.
-
#session_token ⇒ Object
readonly
Returns the value of attribute session_token.
-
#trigger_name ⇒ String
(also: #triggerName)
The name of the trigger (ex. beforeSave, afterSave, etc.).
-
#update ⇒ Hash
The update payload in the request.
-
#user ⇒ Parse::User
The user who performed this request or action.
Instance Method Summary collapse
-
#after_delete? ⇒ Boolean
true if this is a afterDelete webhook trigger request.
-
#after_find? ⇒ Boolean
true if this is a afterFind webhook trigger request.
-
#after_save? ⇒ Boolean
true if this is a afterSave webhook trigger request.
-
#after_trigger? ⇒ Boolean
true if this is a afterSave or afterDelete webhook trigger request.
- #attributes ⇒ ATTRIBUTES
-
#before_delete? ⇒ Boolean
true if this is a beforeDelete webhook trigger request.
-
#before_find? ⇒ Boolean
true if this is a beforeFind webhook trigger request.
-
#before_save? ⇒ Boolean
true if this is a beforeSave webhook trigger request.
-
#before_trigger? ⇒ Boolean
true if this is a beforeSave or beforeDelete webhook trigger request.
-
#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.
-
#error!(msg = "") ⇒ Parse::Webhooks::ResponseError
This method will intentionally raise a ResponseError with a specific message.
-
#function? ⇒ Boolean
true if this is a webhook function request.
-
#initialize(hash = {}) ⇒ Payload
constructor
You would normally never create a Payload object since it is automatically provided to you when using Parse::Webhooks.
-
#inspect ⇒ Object
Redacted inspection.
-
#master? ⇒ Boolean
true if the master key was used for this request.
-
#object? ⇒ Boolean
true if this request is a trigger that contains an object.
-
#original_parse_object ⇒ Parse::Object
A Parse::Object from the original object.
-
#parse_class ⇒ String
The name of the Parse class for this request.
-
#parse_id ⇒ String
(also: #objectId)
The objectId in this request.
-
#parse_object(pristine = false) ⇒ Parse::Object
This method returns a Parse::Object by combining the original object, if was provided, with the final object.
-
#parse_query ⇒ Parse::Query
The Parse query for a beforeFind trigger.
-
#ruby_initiated? ⇒ Boolean
Returns true if this webhook was triggered by a Ruby Parse Stack request.
-
#session_token? ⇒ Boolean
true if this payload carried a caller session token -- i.e.
-
#trigger? ⇒ Boolean
true if this is a webhook trigger request.
-
#user_agent(**opts) ⇒ Parse::Agent?
An opt-in, non-master Agent scoped to the webhook caller's session token.
-
#user_client ⇒ Parse::Client?
An opt-in, user-scoped Client for acting on the server as the webhook's calling user.
-
#wlog(s) ⇒ Object
Method to print to standard that utilizes the an internal id to make it easier to trace incoming requests.
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.
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_name ⇒ String Also known as: functionName
Returns 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_id ⇒ String Also known as: installationId
Returns 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 |
#log ⇒ Object
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 |
#master ⇒ Boolean
Returns whether the master key was used for this request.
64 65 66 |
# File 'lib/parse/webhooks/payload.rb', line 64 def master @master end |
#object ⇒ Hash
In a beforeSave, this attribute is the final object that will be persisted.
64 |
# File 'lib/parse/webhooks/payload.rb', line 64 attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name |
#objects ⇒ Object
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 |
#original ⇒ Hash
In a beforeSave, for previously saved objects, this attribute is the Parse::Object that was previously in the persistent store.
64 |
# File 'lib/parse/webhooks/payload.rb', line 64 attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name |
#params ⇒ Hash
Returns 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 |
#query ⇒ Object
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 |
#raw ⇒ Hash
Returns 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_token ⇒ Object (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_name ⇒ String Also known as: triggerName
Returns 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 |
#update ⇒ Hash
Returns 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 |
#user ⇒ Parse::User
Returns 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.
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.
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.
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.
294 295 296 |
# File 'lib/parse/webhooks/payload.rb', line 294 def after_trigger? after_save? || after_delete? || after_find? end |
#attributes ⇒ ATTRIBUTES
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.
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.
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.
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.
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.
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.
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.
214 215 216 |
# File 'lib/parse/webhooks/payload.rb', line 214 def function? @function_name.present? end |
#inspect ⇒ Object
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.
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.
329 330 331 |
# File 'lib/parse/webhooks/payload.rb', line 329 def object? trigger? && @object.present? end |
#original_parse_object ⇒ Parse::Object
Returns a Parse::Object from the original object.
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_class ⇒ String
Returns 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_id ⇒ String Also known as: objectId
Returns 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.
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_query ⇒ Parse::Query
Returns 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.
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.
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.
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.
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_client ⇒ Parse::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.
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 |