Class: Parse::User

Inherits:
Object show all
Includes:
MFA::UserExtension
Defined in:
lib/parse/model/classes/user.rb,
lib/parse/two_factor_auth/user_extension.rb,
lib/parse/stack/generators/templates/model_user.rb

Overview

Reopen User class to include MFA extension

Constant Summary

Constants inherited from Object

Object::BUILTIN_PARSE_CLASS_NAMES, Object::IDENTIFICATION_FIELDS, Object::VALID_ACL_POLICIES

Constants included from Core::Schema

Core::Schema::DEFAULT_PUBLIC_CLP, Core::Schema::SCHEMA_READONLY_CLASSES

Constants included from Core::Describe

Core::Describe::ALL_SECTIONS, Core::Describe::CORE_FIELD_KEYS, Core::Describe::LOCAL_SECTIONS, Core::Describe::NETWORK_SECTIONS

Constants included from Core::Indexing

Core::Indexing::MAX_INDEXES_PER_COLLECTION, Core::Indexing::PARSE_MANAGED_ARRAY_FIELDS, Core::Indexing::SENSITIVE_FIELDS

Constants included from Core::SearchIndexing

Core::SearchIndexing::ALLOWED_INDEX_TYPES, Core::SearchIndexing::INDEX_NAME_PATTERN

Constants included from Core::Fetching

Core::Fetching::NON_SERIALIZABLE_IVARS

Constants included from Core::EmbedManaged

Core::EmbedManaged::WRITER_KEY

Constants included from Core::ParseReference

Core::ParseReference::OBJECT_ID_LENGTH, Core::ParseReference::SEPARATOR

Constants included from Core::FieldGuards

Core::FieldGuards::GUARD_MODES

Constants included from Properties

Properties::BASE, Properties::BASE_FIELD_MAP, Properties::BASE_KEYS, Properties::CORE_FIELDS, Properties::DELETE_OP, Properties::PROTECTED_INITIALIZE_KEYS, Properties::PROTECTED_MASS_ASSIGNMENT_KEYS, Properties::TYPES

Constants inherited from Pointer

Pointer::ATTRIBUTES, Pointer::OBJECT_ID_FORMAT

Constants inherited from Model

Model::CLASS_AUDIENCE, Model::CLASS_INSTALLATION, Model::CLASS_JOB_SCHEDULE, Model::CLASS_JOB_STATUS, Model::CLASS_PRODUCT, Model::CLASS_PUSH_STATUS, Model::CLASS_ROLE, Model::CLASS_SCHEMA, Model::CLASS_SESSION, Model::CLASS_USER, Model::ID, Model::KEY_CLASS_NAME, Model::KEY_CREATED_AT, Model::KEY_OBJECT_ID, Model::KEY_UPDATED_AT, Model::OBJECT_ID, Model::TYPE_ACL, Model::TYPE_BYTES, Model::TYPE_DATE, Model::TYPE_FIELD, Model::TYPE_FILE, Model::TYPE_GEOPOINT, Model::TYPE_NUMBER, Model::TYPE_OBJECT, Model::TYPE_POINTER, Model::TYPE_POLYGON, Model::TYPE_RELATION

Instance Attribute Summary collapse

Attributes inherited from Object

#acl, #created_at, #id, #updated_at

Attributes inherited from Pointer

#id, #parse_class

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MFA::UserExtension

#confirm_sms_mfa!, #disable_mfa!, #disable_mfa_admin!, #disable_mfa_master_key!, #login_with_mfa!, #mfa_enabled?, #mfa_provisioning_uri, #mfa_qr_code, #mfa_status, #setup_mfa!, #setup_sms_mfa!

Methods inherited from Object

#[], #[]=, #__type, #_resolve_acl_owner_id, #_resolve_default_acl, #acl_changed?, acl_owner_field, acl_policy, acl_policy_setting, #acl_was, #acl_will_change!, #after_create, #after_destroy, #after_save, #after_update, #after_validation, #apply_defaults!, #around_create, #around_destroy, #around_save, #around_update, #around_validation, #as_json, #autofetch_disabled?, #before_create, #before_destroy, #before_save, #before_update, #before_validation, build, #changed, #changed?, class_permissions, #clear_attribute_change!, #clear_changes!, #clear_partial_fetch_state!, default_acls, describe_access, #disable_autofetch!, #enable_autofetch!, #existed?, fetch_clp, #fetched?, #fetched_keys, #fetched_keys=, #field_was_fetched?, #filter_for_user, filter_results_for_user, #fully_fetched?, #has?, #has_selective_keys?, #initialize, #keys, master_only_class!, #nested_fetched_keys, #nested_fetched_keys=, #nested_keys_for, #new?, #parse_class, #partially_fetched?, #persisted?, pointer, #pretty, private_acl!, #reload!, roles_for_user, #rollback!, #run_after_create_callbacks, #run_after_delete_callbacks, #run_after_save_callbacks, #schema, #search_highlights, #search_score, set_class_access, set_clp, set_default_acl, set_default_clp, set_read_user_fields, set_write_user_fields, #twin, unlistable_class!, update_clp!, #updates, #valid?, #validate!, #vector_score, wait_for, watch, webhook, webhook_function

Methods included from Core::Querying

#all, #all_as, #count, #count_distinct, #cursor, #distinct, #each, #find, #find_cached, #first, #first_as, #last_updated, #latest, #literal_where, #newest, #oldest, #query, #scope, #subscribe

Methods included from Core::Schema

#_default_class_level_permissions_for_upgrade, #auto_upgrade!, #create_schema, #fetch_schema, #reset_clp!, #schema, #update_schema

Methods included from Core::Describe

#describe

Methods included from Core::Indexing

#apply_indexes!, #indexes_plan, #mongo_geo_index, #mongo_index, #mongo_index_declarations, #mongo_relation_index

Methods included from Core::SearchIndexing

#apply_search_indexes!, #mongo_search_index, #mongo_search_index_declarations, #search_indexes_plan

Methods included from Core::VectorSearchable

#find_similar

Methods included from Agent::MetadataDSL

#agent_description, #agent_methods, #property_descriptions, #property_enum_descriptions

Methods included from Core::Actions

#_deleted?, #change_requests, #changes_applied!, #changes_payload, #destroy, #destroy_request, #op_add!, #op_add_relation!, #op_add_unique!, #op_destroy!, #op_increment!, #op_remove!, #op_remove_relation!, #operate_field!, #prepare_save!, #relation_change_operations, #save, #save!, #set_attributes!, #update, #update!, #update_relations, #uri_path

Methods included from Core::Fetching

#autofetch!, #fetch, #fetch!, #fetch_cache!, #fetch_json, #fetch_object, #prepare_for_dirty_tracking!

Methods included from Associations::HasMany

has_many, #relation_changes?, #relation_updates, #relations

Methods included from Associations::BelongsTo

belongs_to, #key?

Methods included from Associations::HasOne

has_one

Methods included from Core::ParseReference

format, generate_object_id, parse

Methods included from Core::FieldGuards

#apply_field_guards!

Methods included from Properties

#attribute_changes?, #attribute_updates, #attributes, #attributes=, #field_map, #fields, #format_operation, #format_value

Methods inherited from Pointer

#==, #[], #[]=, #__type, #attributes, #className, #fetch, #fetch_cache!, #fetch_json, #fetch_object, #fetched?, #hash, #initialize, #json_hash, #method_missing, #pointer, #pointer?, #present?, #respond_to_missing?, #search_highlights, #search_score, #sig, #vector_score

Methods inherited from Model

#dirty?, find_class, same_parse_class?

Methods included from Client::Connectable

#client

Constructor Details

This class inherits a constructor from Parse::Object

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Parse::Pointer

Instance Attribute Details

#active_sessionsArray<Parse::Session>

A has_many relationship to all Session instances for this user. This will query the _Session collection for all sessions which have this user in it's user column.

Returns:

Version:

  • 1.7.1



226
# File 'lib/parse/model/classes/user.rb', line 226

has_many :active_sessions, as: :session

#auth_dataHash

The auth data for this Parse::User. Depending on how this user is authenticated or logged in, the contents may be different, especially if you are using another third-party authentication mechanism like Facebook/Twitter.

Returns:

  • (Hash)

    Auth data hash object.



191
# File 'lib/parse/model/classes/user.rb', line 191

property :auth_data, :object

#emailString

Emails are optional in Parse, but if set, they must be unique.

Returns:

  • (String)

    The email field.



196
# File 'lib/parse/model/classes/user.rb', line 196

property :email

#email_verifiedBoolean

Whether this user's email address has been verified. Set by Parse Server when the user follows the verification link delivered by the email adapter, and applied to the in-memory object by #signup! / signup-on-save when the server includes it in the signup response (see +SIGNUP_RESPONSE_APPLY_KEYS+).

Returns:

  • (Boolean)


218
# File 'lib/parse/model/classes/user.rb', line 218

property :email_verified, :boolean

#installationsArray<Parse::Installation>

A has_many query-form association resolving to all Installation records whose user pointer is this user. Useful for targeted push — e.g. sending a notification to every device the user is signed into. This is a query (no column is stored on _User); each access issues a find against _Installation for where(user: self).

Requires a master-key client. Parse Server hardcodes _Installation find to master-key-only at the REST layer, so this association will return an empty array (or fail-closed depending on agent scope) under a session-token-only / sessionless client. The user pointer is also not a reliable owner identity — devices outlive sessions and can change users — see Installation for the full caveat list.



244
# File 'lib/parse/model/classes/user.rb', line 244

has_many :installations, as: :installation

#session_tokenString

Returns The session token if this user is logged in.

Returns:

  • (String)

    The session token if this user is logged in.



184
185
186
# File 'lib/parse/model/classes/user.rb', line 184

def session_token
  @session_token
end

#usernameString

All Parse users have a username and must be globally unique.

Returns:

  • (String)

    The user's username.



209
# File 'lib/parse/model/classes/user.rb', line 209

property :username

Class Method Details

.anonymous_signupUser

Create and log in a new anonymous user via the +authData.anonymous+ provider. The returned user instance has a +session_token+ and an objectId, and #anonymous? returns true. Later, after the user has chosen a username and password, upgrade the account in-place with #upgrade_anonymous!.

Parse Server requires the anonymous-provider payload to include a client-generated +id+; this helper produces one via +SecureRandom.uuid+ so callers don't have to hand-roll the +authData+ shape.

Returns:

  • (User)

    a freshly-created, logged-in anonymous user.

See Also:



1000
1001
1002
# File 'lib/parse/model/classes/user.rb', line 1000

def self.
  autologin_service(:anonymous, { id: SecureRandom.uuid })
end

.autologin_service(service_name, auth_data, body: {}) ⇒ User

Automatically and implicitly signup a user if it did not already exists and authenticates them (login) using third-party authentication data. May raise exceptions similar to create depending on what you provide the body parameter.

Parameters:

  • service_name (Symbol)

    the name of the service key (ex. :facebook)

  • auth_data (Hash)

    the specific service data to place in the user's auth-data for this service.

  • body (Hash) (defaults to: {})

    any additional User related fields or properties when signing up this User record.

Returns:

  • (User)

    a logged in user, or nil.

See Also:



975
976
977
978
979
980
981
982
983
984
985
# File 'lib/parse/model/classes/user.rb', line 975

def self.autologin_service(service_name, auth_data, body: {})
  # Trust-mark this call so {.assert_create_body_safe!} permits the
  # +authData+ that we are explicitly responsible for here. The
  # marker is consumed inside {.create} before forwarding to the
  # server.
  body = body.merge({
    authData: { service_name => auth_data },
    __parse_stack_trusted_authdata: true,
  })
  self.create(body)
end

.create(body, **opts) ⇒ User

Creates a new Parse::User given a hash that maps to the fields defined in your Parse::User collection.

Mass-assignment of +authData+/+auth_data+/+objectId+ is refused. If you intend to create-or-login a user via federated identity, use autologin_service or link_or_create_with_auth_data. Passing those keys directly bypasses the SDK's federated-identity wrapper and risks returning a victim's sessionToken to whoever submitted the request.

Parameters:

  • body (Hash)

    The hash containing the Parse::User fields. The field username and password are required.

  • opts (Hash)

    a customizable set of options

Options Hash (**opts):

  • :master_key (Boolean)

    Whether the master key should be used for this request.

Returns:

  • (User)

    Returns a successfully created Parse::User.

Raises:



900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
# File 'lib/parse/model/classes/user.rb', line 900

def self.create(body, **opts)
  # Consume and clear the SDK-internal trust marker before validation
  # or wire transit. This prevents trusted-authdata flag smuggling
  # through callers that copy hashes from a request parameter.
  trusted = body.is_a?(Hash) ? (body.delete(:__parse_stack_trusted_authdata) ||
                                body.delete("__parse_stack_trusted_authdata")) : false
  assert_create_body_safe!(body) unless trusted
  strip_server_controlled_keys!(body)
  response = client.create_user(body, **opts)
  if response.success?
    body.delete :password # clear password before merging
    # Self-fetch trust: the response.result describes the user we
    # just created, so any returned authData IS that user's own
    # federated-identity payload — allow it through the hydration
    # strip in {#apply_attributes!}.
    return with_authdata_trust { Parse::User.build body.merge(response.result) }
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  end
  raise Parse::Client::ResponseError, response
end

.login(username, password) ⇒ User

Login and return a Parse::User with this username/password combination.

Parameters:

  • username (String)

    the user's username

  • password (String)

    the user's password

Returns:

  • (User)

    a logged in user for the provided username. Returns nil otherwise.

See Also:



1021
1022
1023
1024
1025
1026
1027
# File 'lib/parse/model/classes/user.rb', line 1021

def self.(username, password)
  response = client.(username.to_s, password.to_s)
  return nil unless response.success?
  # Self-fetch trust: the login response IS the authenticating user;
  # any returned authData belongs to them.
  with_authdata_trust { Parse::User.build(response.result) }
end

.login!(username, password) ⇒ User

Login and return a Parse::User with this username/password combination, raising on failure instead of returning nil. Mirrors the find_by_username! / find! conventions: callers who treat an unsuccessful login as an exceptional condition shouldn't have to build their own raise if .nil? boilerplate around every call site.

Parameters:

  • username (String)

    the user's username.

  • password (String)

    the user's password.

Returns:

  • (User)

    the logged-in user.

Raises:

  • (Parse::Error::AuthenticationError)

    when Parse Server rejects the credentials, the request is rate-limited at the server, or the response is otherwise unsuccessful.

See Also:



1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
# File 'lib/parse/model/classes/user.rb', line 1042

def self.login!(username, password)
  response = client.(username.to_s, password.to_s)
  if response.success?
    # Self-fetch trust: see {.login}.
    with_authdata_trust { Parse::User.build(response.result) }
  else
    raise Parse::Error::AuthenticationError,
          "Parse::User.login! failed for #{username.inspect}: " \
          "#{response.error || "HTTP #{response.http_status}"} (code=#{response.code.inspect})"
  end
end

.master_only_fields(*fields) ⇒ Array<Symbol>

Hide one or more fields from query/get responses for all non-master callers, including the owning user themselves. Useful for admin-only metadata living on _User (e.g. internal scoring, moderation notes).

Requires Parse Server option protectedFieldsOwnerExempt: false. With the default true, the owning user still sees these fields on their own row.

Examples:

class Parse::User
  property :my_opinion_of_them, :string
  master_only_fields :my_opinion_of_them
end

Parameters:

  • fields (Array<Symbol,String>)

    field names. Use snake_case Ruby property names; they're auto-converted to camelCase.

Returns:

  • (Array<Symbol>)

    full master-only field list after this call.



351
352
353
354
355
356
357
# File 'lib/parse/model/classes/user.rb', line 351

def master_only_fields(*fields)
  @master_only_fields ||= []
  @master_only_fields = (@master_only_fields + fields.flatten.map(&:to_sym)).uniq
  _warn_about_owner_exempt_prereq!
  _rebuild_user_protected_fields!
  @master_only_fields.dup
end

.protect_fields(pattern, fields) ⇒ Object

Override Object.protect_fields on _User so that ad-hoc uses (i.e. not through master_only_fields / self_visible_fields) emit a one-time advisory pointing at the higher-level helpers and the protectedFieldsOwnerExempt flag. The behavior is otherwise unchanged.



393
394
395
396
# File 'lib/parse/model/classes/user.rb', line 393

def protect_fields(pattern, fields)
  _warn_about_user_protect_fields! unless @_user_field_dsl_active
  super
end

.request_password_reset(email) ⇒ Boolean

Request a password reset for a registered email.

Examples:

user = Parse::User.first

# pass a user object
Parse::User.request_password_reset user
# or email
Parse::User.request_password_reset("user@example.com")

Parameters:

  • email (String)

    The user's email address.

Returns:

  • (Boolean)

    True/false if successful.



1064
1065
1066
1067
1068
1069
# File 'lib/parse/model/classes/user.rb', line 1064

def self.request_password_reset(email)
  email = email.email if email.is_a?(Parse::User)
  return false if email.blank?
  response = client.request_password_reset(email)
  response.success?
end

.self_visible_fields(*fields, via: :self) ⇒ Array<Symbol>

Hide one or more fields from public/role/other-user callers, but allow the owning user to see them. Useful for private profile data that belongs to the user (e.g. preferences, private notes).

Requires:

  • Parse Server option protectedFieldsOwnerExempt: false.
  • A self-pointer field on _User (named via via:, default :self) that is set to the row's own pointer by a beforeSave('_User') Cloud Code trigger.

Examples:

class Parse::User
  property :favorite_color, :string
  self_visible_fields :favorite_color, via: :self
end

Parameters:

  • fields (Array<Symbol,String>)

    field names. snake_case OK.

  • via (Symbol, String) (defaults to: :self)

    name of the self-pointer field on _User (default :self).

Returns:



378
379
380
381
382
383
384
385
386
# File 'lib/parse/model/classes/user.rb', line 378

def self_visible_fields(*fields, via: :self)
  @self_visible_fields ||= []
  @self_visible_fields = (@self_visible_fields + fields.flatten.map(&:to_sym)).uniq
  @self_pointer_field = via.to_sym
  _warn_about_owner_exempt_prereq!
  _warn_about_self_pointer_prereq!(via)
  _rebuild_user_protected_fields!
  @self_visible_fields.dup
end

.session(token, opts = {}) ⇒ User

Same as session! but returns nil if a user was not found or sesion token was invalid.

Returns:

  • (User)

    the user matching this active token, otherwise nil.

See Also:

  • #session!


1074
1075
1076
1077
1078
# File 'lib/parse/model/classes/user.rb', line 1074

def self.session(token, opts = {})
  self.session! token, opts
rescue Parse::Error::InvalidSessionTokenError
  nil
end

.session!(token, opts = {}) ⇒ User

Return a Parse::User for this active session token.

Returns:

  • (User)

    the user matching this active token

Raises:

  • (InvalidSessionTokenError)

    Invalid session token.

  • (ArgumentError)

    when opts smuggles a conflicting :session_token key — the positional token argument is the only source of truth; rejecting the kwarg prevents a silent override that would authenticate as a different user.

See Also:



1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
# File 'lib/parse/model/classes/user.rb', line 1088

def self.session!(token, opts = {})
  if opts.is_a?(Hash) && (opts.key?(:session_token) || opts.key?("session_token"))
    raise ArgumentError,
          "Parse::User.session! takes the session token as its positional " \
          "argument; do not also pass it via opts[:session_token]"
  end
  # support Parse::Session objects
  token = token.session_token if token.respond_to?(:session_token)
  response = client.current_user(token, **opts)
  return nil unless response.success?
  # Self-fetch trust: `/users/me` returns the row owned by the
  # supplied session token, so authData here is that user's own.
  with_authdata_trust { Parse::User.build(response.result) }
end

.signup(username, password, email = nil, body: {}) ⇒ Object

This method will signup a new user using the parameters below. The required fields to create a user in Parse is the username and password fields. The email field is optional. Both username and email (if provided), must be unique. At a minimum, it is recommended you perform a query using the supplied username first to verify do not already have an account with that username. This method will raise all the exceptions from the similar create method.

See Also:



1010
1011
1012
1013
1014
# File 'lib/parse/model/classes/user.rb', line 1010

def self.(username, password, email = nil, body: {})
  body = body.merge({ username: username, password: password })
  body[:email] = email if email.present?
  self.create(body)
end

.signup_body_self_only_acl_safe?(body) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

True when the signup-body objectId and ACL together describe the safe self-only ownership pattern that Object.acl_policy produces under owner: :self: the body has a client-assigned objectId matching the Parse-id format, and the ACL has exactly one entry granting read+write to that same objectId. Any deviation — multiple keys, a non-self key, a * (public) entry, a role: entry, missing or extra permissions — fails the check and the strip-everything fallback in #signup_create / #signup! runs as before.

Parameters:

  • body (Hash)

    signup request body, with symbol or string keys.

Returns:

  • (Boolean)


1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
# File 'lib/parse/model/classes/user.rb', line 1344

def self.(body)
  return false unless body.is_a?(Hash)
  oid = body[:objectId] || body["objectId"]
  acl = body[:ACL] || body["ACL"]
  return false unless oid.is_a?(String) && oid.match?(PARSE_OBJECT_ID_FORMAT)
  return false unless acl.is_a?(Hash) && acl.size == 1
  perms = acl[oid] || acl[oid.to_s]
  return false unless perms.is_a?(Hash)
  normalized = perms.transform_keys(&:to_s)
  normalized == { "read" => true, "write" => true }
end

Instance Method Details

#acl_roles(max_depth: 10, master: false, as: nil) ⇒ Set<String>

Return the transitive upward closure of role names this user inherits permissions from.

Authorization

The role graph is privileged data: Parse Server's _Role class ships with acl_policy :private precisely so anonymous clients cannot enumerate role memberships. This method therefore routes through the mongo-direct fast path under an EXPLICIT authorization scope.

By default, as: is set to self — the user instance itself, meaning "I (this user) am asking about my own roles". The scope is resolved via ACLScope and CLP is enforced against _Role: the call succeeds iff the user's permission set (["*", user.id, "role:..."]) is permitted to find on _Role. Under Parse Server's default _Role CLP (master-only, which Role's acl_policy :private does not change), the user's scope is NOT permitted, so this call raises CLPScope::Denied. Apps that have explicitly opened _Role CLP for authenticated users (e.g. find: { requiresAuthentication: true }) will have the call succeed.

Callers performing privileged work (computing ACL permission sets, e.g. server-side filters) should pass master: true to bypass the CLP check.

Breaking change: Previously this method bypassed the authorization check entirely (callers could construct a Parse::User with any objectId via Parse::User.new.tap { |u| u.id = victim_id } and enumerate the victim's roles). The new contract is explicit-auth-required; use master: true for the previous behavior.

Examples:

# User reading their own roles (subject to _Role CLP):
permission_set = (["*", user.id] + user.acl_roles.map { |n| "role:#{n}" }).uniq
# Admin/SDK-internal code building ACL filters:
permission_set = (["*", user.id] + user.acl_roles(master: true).map { |n| "role:#{n}" }).uniq

Parameters:

  • max_depth (Integer) (defaults to: 10)

    maximum BFS depth (default: 10).

  • master (Boolean) (defaults to: false)

    when +true+, bypass _Role CLP and run the role-graph lookup under master mode. Use for ACL-building code paths inside the SDK or in admin tooling.

  • as (Parse::User, Parse::Pointer, nil) (defaults to: nil)

    caller-scope. When nil, defaults to self (the user-asking-about-their-own-roles case). Pass a different user to ask "what would this caller see when introspecting this user's roles?"; the scope's permission set is checked against _Role CLP.

Returns:

  • (Set<String>)

    role names (no +role:+ prefix). Empty set when the user has no objectId yet or holds no roles.

Raises:



1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
# File 'lib/parse/model/classes/user.rb', line 1287

def acl_roles(max_depth: 10, master: false, as: nil)
  return Set.new unless id.is_a?(String) && !id.empty?
  # Default `as:` to self so the common "user reading their own
  # roles" case works without ceremony when _Role CLP permits the
  # user. The CLP check + scope resolution happens inside
  # Parse::Role.all_for_user → Parse::MongoDB.role_names_for_user.
  effective_as = as.nil? && master != true ? self : as
  Parse::Role.all_for_user(
    self, max_depth: max_depth, master: master, as: effective_as,
  )
end

#active_session_countInteger

Get the count of active (non-expired) sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).

Examples:

count = user.active_session_count
puts "User is logged in on #{count} devices"

Returns:

  • (Integer)

    the number of active sessions

Raises:



1201
1202
1203
1204
1205
1206
1207
# File 'lib/parse/model/classes/user.rb', line 1201

def active_session_count
  return 0 unless id.present?
  require_self_session!("active_session_count")
  Parse.with_session(@session_token) do
    Parse::Session.active_count_for_user(self)
  end
end

#anonymous?Boolean

Returns true if this user is anonymous (i.e. created via the +authData.anonymous+ provider rather than via signup with a username/password or a real OAuth provider).

Returns:

  • (Boolean)

    true if this user is anonymous (i.e. created via the +authData.anonymous+ provider rather than via signup with a username/password or a real OAuth provider).



519
520
521
# File 'lib/parse/model/classes/user.rb', line 519

def anonymous?
  !anonymous_id.nil?
end

#anonymous_idString

Returns the anonymous identifier only if this user is anonymous.

Returns:

  • (String)

    The anonymous identifier for this anonymous user.

See Also:



526
527
528
# File 'lib/parse/model/classes/user.rb', line 526

def anonymous_id
  auth_data["anonymous"]["id"] if auth_data.present? && auth_data["anonymous"].is_a?(Hash)
end

#any_session!String

If the current session token for this instance is nil, this method finds the most recent active Parse::Session token for this user and applies it to the instance. The user instance will now be authenticated and logged in with the selected session token. Useful if you need to call save or destroy methods on behalf of a logged in user.

Returns:

  • (String)

    The session token or nil if no session was found for this user.



1133
1134
1135
1136
1137
1138
1139
# File 'lib/parse/model/classes/user.rb', line 1133

def any_session!
  unless @session_token.present?
    _active_session = active_sessions(restricted: false, order: :updated_at.desc).first
    self.session_token = _active_session.session_token if _active_session.present?
  end
  @session_token
end

Adds the third-party authentication data to for a given service.

Parameters:

  • service_name (Symbol)

    The name of the service (ex. :facebook)

  • data (Hash)

    The body of the OAuth data. Dependent on each service.

Raises:



534
535
536
537
538
# File 'lib/parse/model/classes/user.rb', line 534

def link_auth_data!(service_name, **data)
  response = client.set_service_auth_data(id, service_name, data)
  raise Parse::Client::ResponseError, response if response.error?
  self.class.with_authdata_trust { apply_attributes!(response.result) }
end

#logged_in?Boolean

Returns true if this user has a session token.

Returns:

  • (Boolean)

    true if this user has a session token.



651
652
653
# File 'lib/parse/model/classes/user.rb', line 651

def logged_in?
  self.session_token.present?
end

#login!(passwd = nil) ⇒ Boolean

Login and get a session token for this user.

Parameters:

  • passwd (String) (defaults to: nil)

    The password for this user.

Returns:

  • (Boolean)

    True/false if we received a valid session token.



778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'lib/parse/model/classes/user.rb', line 778

def login!(passwd = nil)
  self.password = passwd || self.password
  response = client.(username.to_s, password.to_s)
  if response.success?
    # Unlike signup, login's response is the canonical state of an
    # existing user, including any linked authData. Applying the
    # full response body here is intentional -- the server is
    # telling us what the account currently looks like. (Compare
    # signup, where we narrow to an allow-list because a brand-new
    # account has no legitimate authData to report.)
    self.class.with_authdata_trust { apply_attributes! response.result }
    # Drop the plaintext password from memory now that the login
    # has succeeded. Direct ivar assignment so the dirty tracker
    # doesn't record this clear as a pending change.
    @password = nil
    # Clear dirty state so a subsequent user.save! does not re-send
    # `password` (which Parse Server would treat as a password
    # change and use to revoke the session this login just issued).
    # See the matching note in #signup!.
    changes_applied!
    clear_partial_fetch_state!
  end
  self.session_token.present?
end

#logoutBoolean

Invalid the current session token for this logged in user.

Returns:

  • (Boolean)

    True/false if successful



805
806
807
808
809
810
811
812
# File 'lib/parse/model/classes/user.rb', line 805

def logout
  return true if self.session_token.blank?
  client.logout session_token
  self.session_token = nil
  true
rescue
  false
end

#logout_all!(keep_current: false) ⇒ Integer

Logout from all sessions, effectively signing out on all devices. Optionally keep the current session active.

Self-guard. Requires the user instance to carry a session token — i.e. to have been obtained via login/signup or attached via #session_token=. Without this, user.id = victim_id; user.logout_all! could revoke another user's sessions if the deployment has loose _Session write CLP. The guard fails closed in the SDK so the deployment's CLP isn't the only line of defense.

Examples:

# Logout from all devices
user.logout_all!

# Logout from all devices except current
user.logout_all!(keep_current: true)

Parameters:

  • keep_current (Boolean) (defaults to: false)

    if true, keeps the current session active (default: false)

Returns:

  • (Integer)

    the number of sessions revoked

Raises:



1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
# File 'lib/parse/model/classes/user.rb', line 1164

def logout_all!(keep_current: false)
  return 0 unless id.present?
  require_self_session!("logout_all!")
  current_token = @session_token
  # Self-scope the _Session query: in client mode the ambient client
  # has no auth, so the query must carry this user's session token to
  # be authorized against /classes/_Session. Master-key mode ignores
  # the ambient since the master key still wins.
  count = Parse.with_session(current_token) do
    # Always revoke the OTHER sessions first under the live token —
    # destroying the calling session mid-loop invalidates the token
    # and the remaining deletes 401. Then, if not keeping current,
    # close the calling session via the dedicated logout endpoint.
    n = Parse::Session.revoke_all_for_user(self, except: current_token)
    unless keep_current
      begin
        Parse.client.logout(current_token)
        n += 1
      rescue Parse::Error::InvalidSessionTokenError
        # The calling session was already gone (server-side TTL or
        # concurrent revoke). Idempotent: count what we destroyed.
      end
    end
    n
  end
  @session_token = nil unless keep_current
  @session = nil unless keep_current
  count
end

#multi_session?Boolean

Check if this user has multiple active sessions (logged in on multiple devices).

Examples:

if user.multi_session?
  puts "User is logged in on multiple devices"
end

Returns:

  • (Boolean)

    true if user has more than one active session



1231
1232
1233
# File 'lib/parse/model/classes/user.rb', line 1231

def multi_session?
  active_session_count > 1
end

#password=(value) ⇒ String

You may set a password for this user when you are creating them. Parse never returns a Parse::User's password when a record is fetched. Therefore, normally this getter is nil. While this API exists, it is recommended you use either the #login! or #signup! methods. (see #login!)

Returns:

  • (String)

    The password you set.



204
# File 'lib/parse/model/classes/user.rb', line 204

property :password

#request_password_resetBoolean

Request a password reset for this user

Returns:

  • (Boolean)

    true if it was successful requested. false otherwise.

See Also:



658
659
660
661
# File 'lib/parse/model/classes/user.rb', line 658

def request_password_reset
  return false if email.nil?
  Parse::User.request_password_reset(email)
end

#sessionSession

Returns the session corresponding to the user's session token.

Returns:

  • (Session)

    the session corresponding to the user's session token.



821
822
823
824
825
826
827
828
829
830
831
832
# File 'lib/parse/model/classes/user.rb', line 821

def session
  if @session.blank? && @session_token.present?
    response = client.fetch_session(@session_token)
    # Trusted hydration: +response.result+ is the server-side
    # _Session row, which legitimately includes +sessionToken+,
    # +createdAt+, +updatedAt+, and other protected keys. Route
    # through {Parse::Object.build} which handles the trusted-init
    # signalling.
    @session ||= Parse::Object.build(response.result, Parse::Model::CLASS_SESSION)
  end
  @session
end

#sessionsArray<Parse::Session>

Get all active sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).

Examples:

user.sessions.each do |session|
  puts "Session created: #{session.created_at}"
end

Returns:

Raises:



1217
1218
1219
1220
1221
1222
1223
# File 'lib/parse/model/classes/user.rb', line 1217

def sessions
  return [] unless id.present?
  require_self_session!("sessions")
  Parse.with_session(@session_token) do
    Parse::Session.for_user(self).all
  end
end

#signup!(passwd = nil) ⇒ Boolean

You may set a password for this user when you are creating them. Parse never returns a

Parameters:

  • passwd (defaults to: nil)

    The user's password to be used for signing up.

Returns:

  • (Boolean)

    True if signup it was successful. If it fails an exception is thrown.

Raises:



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# File 'lib/parse/model/classes/user.rb', line 672

def signup!(passwd = nil)
  self.password = passwd || password
  if username.blank?
    raise Parse::Error::UsernameMissingError, "Signup requires a username."
  end

  if password.blank?
    raise Parse::Error::PasswordMissingError, "Signup requires a password."
  end

   = attribute_updates
  # See {#signup_create} for the rationale on the safe-pattern check.
  if self.class.()
    .except!(:createdAt, :updatedAt, "createdAt", "updatedAt")
  else
    .except!(*Parse::Properties::BASE_FIELD_MAP.flatten)
  end
  self.class.strip_server_controlled_keys!()

  # first signup the user, then save any additional attributes
  response = client.create_user 

  if response.success?
    # Restrict what the server can plant into the in-memory user via
    # the signup response, matching the defense in {#signup_create}.
    # `POST /parse/users` legitimately returns objectId, createdAt,
    # updatedAt (extracted into @-vars directly below), sessionToken,
    # and emailVerified. Any other key in the response body --
    # `authData`, `_rperm`, `_wperm`, `roles`, etc. -- is dropped, so
    # a compromised or MITM'd Parse Server cannot use this code path
    # to plant credentials/permissions onto the user we just signed
    # up. The previous `apply_attributes! response.result` accepted
    # every key the server returned through the typed property
    # writers (`authData_set_attribute!` exists because we declare
    # `property :auth_data, :object`), which was a footgun the
    # save-as-signup path had already addressed.
    result = response.result
    @id = result[Parse::Model::OBJECT_ID] || @id
    @created_at = result["createdAt"] || @created_at
    @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
    set_attributes!(result.slice(*SIGNUP_RESPONSE_APPLY_KEYS))
    # Drop the plaintext password from memory now that the server
    # has it hashed and we no longer need it. Matches the Parse JS
    # SDK behavior of clearing the password attribute after a
    # successful save/signup. Uses direct ivar assignment so the
    # dirty tracker doesn't record this clear as a pending change
    # that would be re-sent on the next save.
    @password = nil
    # Mirror Parse::Object#save: a successful round-trip means the
    # locally-set credential fields are now in sync with the server
    # and must NOT be re-sent on the next save. Without this, a
    # subsequent user.save! re-transmits `password`, which Parse
    # Server treats as a password change under
    # revokeSessionOnPasswordReset and revokes the session just
    # minted by this signup.
    changes_applied!
    clear_partial_fetch_state!
    return true
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  when Parse::Response::ERROR_EMAIL_INVALID
    raise Parse::Error::InvalidEmailAddress, response
  end
  raise Parse::Client::ResponseError, response
end

#signup_on_saveBoolean

When true (default), saving a new Parse::User that has a password value routes through Parse Server's signup endpoint (POST /parse/users) with the X-Parse-Revocable-Session header set, so the signup response returns a session token that is applied to the in-memory user object via the standard sessionToken_set_attribute! hydration path. Without this flag, Parse::User.new(...).save! left session_token nil because the underlying create path did not request a revocable session.

Set to false to always create users without requesting a revocable session token - for example, when a master-key server-side script is provisioning user rows that will receive credentials later. New users created with no password always fall through to the standard create path regardless of this flag.

auth_data (federated identity / OAuth) signup is deliberately NOT triggered by this flag. POST /parse/users treats auth_data as a claim against an existing account, so allowing mass-assigned auth_data to trigger a revocable-session signup would let attacker-controlled params plant another user's session token onto the in-memory object. Use autologin_service or signup (the explicit class methods) for OAuth-driven signup; both bypass the mass-assignment filter because the caller is explicitly choosing the federated-identity flow.

Inherited through subclasses via ActiveSupport::Concern's class_attribute, so an application-specific subclass may override the default without affecting Parse::User itself.

Returns:

  • (Boolean)


180
# File 'lib/parse/model/classes/user.rb', line 180

class_attribute :signup_on_save, instance_writer: false

Removes third-party authentication data for this user

Parameters:

  • service_name (Symbol)

    The name of the third-party service (ex. :facebook)

Returns:

  • (Boolean)

    True/false if successful.

Raises:



544
545
546
547
548
# File 'lib/parse/model/classes/user.rb', line 544

def unlink_auth_data!(service_name)
  response = client.set_service_auth_data(id, service_name, nil)
  raise Parse::Client::ResponseError, response if response.error?
  self.class.with_authdata_trust { apply_attributes!(response.result) }
end

#upgrade_anonymous!(username:, password:, email: nil) ⇒ Boolean

Upgrade an anonymous user (one created via the +authData.anonymous+ provider) into a full username/password account. This is the SDK-side counterpart of the Parse JS SDK's +_linkWith('username', ...)+ flow — it sends a single +PUT /users/:id+ with the new credentials and an explicit +authData: { anonymous: nil }+ unlink in the same body, then narrowly applies the server's response to the in-memory user.

The +authData.anonymous+ unlink is essential: leaving the anonymous provider attached after assigning a username would let anyone else who somehow learned the (random) anonymous id silently log in as the freshly-named account, a documented Parse foot-gun.

Parameters:

  • username (String)

    the username to claim. Must be unique.

  • password (String)

    the password to set on the account.

  • email (String, nil) (defaults to: nil)

    optional email address. Must be unique if provided.

Returns:

  • (Boolean)

    true on success.

Raises:



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/parse/model/classes/user.rb', line 579

def upgrade_anonymous!(username:, password:, email: nil)
  require_self_session!(:upgrade_anonymous!)
  if @id.nil? || @id.to_s.empty?
    raise Parse::Error::AuthenticationError,
          "Parse::User#upgrade_anonymous! requires a saved user (no objectId)"
  end
  unless anonymous?
    raise Parse::Error::AuthenticationError,
          "Parse::User#upgrade_anonymous! is only valid for anonymous users " \
          "(authData.anonymous is not present on this instance)"
  end
  if username.nil? || username.to_s.empty?
    raise Parse::Error::UsernameMissingError, "upgrade_anonymous! requires a username."
  end
  if password.nil? || password.to_s.empty?
    raise Parse::Error::PasswordMissingError, "upgrade_anonymous! requires a password."
  end

  body = {
    username: username.to_s,
    password: password.to_s,
    # Explicitly unlink the anonymous provider in the same request
    # that claims the new credentials — otherwise the account
    # remains takeover-able via the anonymous id.
    authData: { anonymous: nil },
  }
  body[:email] = email.to_s if email.is_a?(String) && !email.empty?

  response = client.update_user(@id, body, session_token: @session_token)

  if response.success?
    result = response.result || {}
    @updated_at = result["updatedAt"] || @updated_at
    # Parse Server may rotate the session token on a credential
    # change; apply it narrowly if present without going through the
    # full property writer chain.
    if result["sessionToken"].is_a?(String) && !result["sessionToken"].empty?
      @session_token = result["sessionToken"]
    end
    @auth_data.delete("anonymous") if @auth_data.is_a?(Hash)
    @username = username.to_s
    @email = email.to_s if email.is_a?(String) && !email.empty?
    @password = nil
    changes_applied!
    clear_partial_fetch_state!
    return true
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  when Parse::Response::ERROR_EMAIL_INVALID
    raise Parse::Error::InvalidEmailAddress, response
  end
  raise Parse::Client::ResponseError, response
end

#with_session { ... } ⇒ Object

Block-scoped sugar around Parse.with_session: runs the block with this user's session_token as the ambient session token for the current fiber. Every Parse call inside the block that doesn't explicitly pass session_token: or use_master_key: true will be sent as this user.

Examples:

user = Parse::User.login!("alice", "pw")
user.with_session do
  Post.all                # scoped to alice
  post.save               # scoped to alice
end

Yields:

  • runs the block with the user's session in ambient scope.

Returns:

  • (Object)

    the block's return value.

Raises:



1118
1119
1120
1121
1122
1123
1124
1125
1126
# File 'lib/parse/model/classes/user.rb', line 1118

def with_session(&block)
  raise ArgumentError, "Parse::User#with_session requires a block" unless block_given?
  unless @session_token.is_a?(String) && !@session_token.empty?
    raise Parse::Error::AuthenticationError,
          "Parse::User#with_session requires an authenticated session — " \
          "obtain the instance via login/signup or call `user.session_token = '...'` first"
  end
  Parse.with_session(@session_token, &block)
end