Class: Parse::User
- 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
-
#active_sessions ⇒ Array<Parse::Session>
A has_many relationship to all Session instances for this user.
-
#auth_data ⇒ Hash
The auth data for this Parse::User.
-
#email ⇒ String
Emails are optional in Parse, but if set, they must be unique.
-
#email_verified ⇒ Boolean
Whether this user's email address has been verified.
-
#installations ⇒ Array<Parse::Installation>
A
has_manyquery-form association resolving to all Installation records whoseuserpointer is this user. -
#session_token ⇒ String
readonly
The session token if this user is logged in.
-
#username ⇒ String
All Parse users have a username and must be globally unique.
Attributes inherited from Object
#acl, #created_at, #id, #updated_at
Attributes inherited from Pointer
Class Method Summary collapse
-
.anonymous_signup ⇒ User
Create and log in a new anonymous user via the +authData.anonymous+ provider.
-
.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.
-
.create(body, **opts) ⇒ User
Creates a new Parse::User given a hash that maps to the fields defined in your Parse::User collection.
-
.login(username, password) ⇒ User
Login and return a Parse::User with this username/password combination.
-
.login!(username, password) ⇒ User
Login and return a Parse::User with this username/password combination, raising on failure instead of returning nil.
-
.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.
-
.protect_fields(pattern, fields) ⇒ Object
Override Object.protect_fields on
_Userso that ad-hoc uses (i.e. not through User.master_only_fields / User.self_visible_fields) emit a one-time advisory pointing at the higher-level helpers and theprotectedFieldsOwnerExemptflag. -
.request_password_reset(email) ⇒ Boolean
Request a password reset for a registered email.
-
.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.
-
.session(token, opts = {}) ⇒ User
Same as
session!but returns nil if a user was not found or sesion token was invalid. -
.session!(token, opts = {}) ⇒ User
Return a Parse::User for this active session token.
-
.signup(username, password, email = nil, body: {}) ⇒ Object
This method will signup a new user using the parameters below.
-
.signup_body_self_only_acl_safe?(body) ⇒ Boolean
private
True when the signup-body
objectIdandACLtogether describe the safe self-only ownership pattern that Object.acl_policy produces underowner: :self: the body has a client-assignedobjectIdmatching the Parse-id format, and the ACL has exactly one entry granting read+write to that same objectId.
Instance Method Summary collapse
-
#acl_roles(max_depth: 10, master: false, as: nil) ⇒ Set<String>
Return the transitive upward closure of role names this user inherits permissions from.
-
#active_session_count ⇒ Integer
Get the count of active (non-expired) sessions for this user.
-
#anonymous? ⇒ 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).
-
#anonymous_id ⇒ String
Returns the anonymous identifier only if this user is anonymous.
-
#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.
-
#link_auth_data!(service_name, **data) ⇒ Object
Adds the third-party authentication data to for a given service.
-
#logged_in? ⇒ Boolean
True if this user has a session token.
-
#login!(passwd = nil) ⇒ Boolean
Login and get a session token for this user.
-
#logout ⇒ Boolean
Invalid the current session token for this logged in user.
-
#logout_all!(keep_current: false) ⇒ Integer
Logout from all sessions, effectively signing out on all devices.
-
#multi_session? ⇒ Boolean
Check if this user has multiple active sessions (logged in on multiple devices).
-
#password=(value) ⇒ String
You may set a password for this user when you are creating them.
-
#request_password_reset ⇒ Boolean
Request a password reset for this user.
-
#session ⇒ Session
The session corresponding to the user's session token.
-
#sessions ⇒ Array<Parse::Session>
Get all active sessions for this user.
-
#signup!(passwd = nil) ⇒ Boolean
You may set a password for this user when you are creating them.
-
#signup_on_save ⇒ Boolean
When true (default), saving a new User that has a
passwordvalue routes through Parse Server's signup endpoint (POST /parse/users) with theX-Parse-Revocable-Sessionheader set, so the signup response returns a session token that is applied to the in-memory user object via the standardsessionToken_set_attribute!hydration path. -
#unlink_auth_data!(service_name) ⇒ Boolean
Removes third-party authentication data for this user.
-
#upgrade_anonymous!(username:, password:, email: nil) ⇒ Boolean
Upgrade an anonymous user (one created via the +authData.anonymous+ provider) into a full username/password account.
-
#with_session { ... } ⇒ Object
Block-scoped sugar around with_session: runs the block with this user's
session_tokenas the ambient session token for the current fiber.
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
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
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
Methods included from Associations::HasOne
Methods included from Core::ParseReference
format, generate_object_id, parse
Methods included from Core::FieldGuards
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
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_sessions ⇒ Array<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.
226 |
# File 'lib/parse/model/classes/user.rb', line 226 has_many :active_sessions, as: :session |
#auth_data ⇒ Hash
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.
191 |
# File 'lib/parse/model/classes/user.rb', line 191 property :auth_data, :object |
#email ⇒ String
Emails are optional in Parse, but if set, they must be unique.
196 |
# File 'lib/parse/model/classes/user.rb', line 196 property :email |
#email_verified ⇒ Boolean
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+).
218 |
# File 'lib/parse/model/classes/user.rb', line 218 property :email_verified, :boolean |
#installations ⇒ Array<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_token ⇒ String
Returns 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 |
#username ⇒ String
All Parse users have a username and must be globally unique.
209 |
# File 'lib/parse/model/classes/user.rb', line 209 property :username |
Class Method Details
.anonymous_signup ⇒ User
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.
1000 1001 1002 |
# File 'lib/parse/model/classes/user.rb', line 1000 def self.anonymous_signup 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.
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.
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.
1021 1022 1023 1024 1025 1026 1027 |
# File 'lib/parse/model/classes/user.rb', line 1021 def self.login(username, password) response = client.login(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.
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.login(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.
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.
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 viavia:, default:self) that is set to the row's own pointer by abeforeSave('_User')Cloud Code trigger.
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.
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.
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.
1010 1011 1012 1013 1014 |
# File 'lib/parse/model/classes/user.rb', line 1010 def self.signup(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.
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 |
# File 'lib/parse/model/classes/user.rb', line 1344 def self.signup_body_self_only_acl_safe?(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.
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_count ⇒ Integer
Get the count of active (non-expired) sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).
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).
519 520 521 |
# File 'lib/parse/model/classes/user.rb', line 519 def anonymous? !anonymous_id.nil? end |
#anonymous_id ⇒ String
Returns the anonymous identifier only if this user is anonymous.
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.
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 |
#link_auth_data!(service_name, **data) ⇒ Object
Adds the third-party authentication data to for a given service.
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.
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.
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.login(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 |
#logout ⇒ Boolean
Invalid the current session token for this logged in user.
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.
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).
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!)
204 |
# File 'lib/parse/model/classes/user.rb', line 204 property :password |
#request_password_reset ⇒ Boolean
Request a password reset for this user
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 |
#session ⇒ Session
Returns 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 |
#sessions ⇒ Array<Parse::Session>
Get all active sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).
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
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 signup_attrs = attribute_updates # See {#signup_create} for the rationale on the safe-pattern check. if self.class.signup_body_self_only_acl_safe?(signup_attrs) signup_attrs.except!(:createdAt, :updatedAt, "createdAt", "updatedAt") else signup_attrs.except!(*Parse::Properties::BASE_FIELD_MAP.flatten) end self.class.strip_server_controlled_keys!(signup_attrs) # first signup the user, then save any additional attributes response = client.create_user signup_attrs 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_save ⇒ Boolean
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.
180 |
# File 'lib/parse/model/classes/user.rb', line 180 class_attribute :signup_on_save, instance_writer: false |
#unlink_auth_data!(service_name) ⇒ Boolean
Removes third-party authentication data for this user
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.
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.
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 |