Module: Parse::Authorization

Defined in:
lib/parse/authorization.rb

Overview

Resolution of a caller's identity and inherited roles, and the caches that make that resolution cheap.

Why this is not part of Atlas Search. It used to be. Session-token resolution and role-closure expansion were written for Parse::AtlasSearch, because $search was the first thing that ran aggregations straight against MongoDB and therefore the first thing that had to enforce ACLs itself. Everything since has reached back through it: Parse::ACLScope called Parse::AtlasSearch::Session.resolve, and Parse::MongoDB.aggregate calls Parse::ACLScope, so Parse::Query#results_direct on a plain query with no $search anywhere in it depended on the Atlas Search namespace to decide who the caller was. That is backwards. Deciding who someone is and what they may read is authorization infrastructure, and Atlas Search is one consumer of it:

Atlas Search ─┐
Aggregates  ──┼─> Parse::Authorization ─> identity / role caches
Direct query ─┘

Policy that is genuinely about Atlas Search stays on Atlas Search: Parse::AtlasSearch.require_session_token decides whether $search may run anonymously, which is a question about that feature, not about identity.

Two caches, because they invalidate on different events.

* The **identity plane** maps a session token to a user id. Long TTL
(1 hour), invalidated by logout and by a `_User` write. Named
`identity_cache` rather than `session_cache` because it stores neither
`_Session` rows nor session objects: it stores one string per token.
The old name led readers to reason about `_Session` semantics that
were never involved.

* The **role plane** maps a user id to a `Set` of role names. Short TTL
(30 seconds), invalidated by any `_Role` write. Stale entries here
produce wrong ACL decisions rather than merely slow ones, so the
default is conservative.

State belongs to a client, not to the process. A Context is owned by one Client and reachable as client.authorization. Two named clients pointed at two Parse applications must never resolve a token against each other's caches or each other's /users/me, which is exactly what a set of module-level globals allowed. Authorization.configure exists as a boundary convenience and configures the DEFAULT client's context; below the boundary, client: is required and has no default.

Defined Under Namespace

Classes: Context, InvalidSession, MemoryCache, Resolved

Class Method Summary collapse

Class Method Details

.configure(**kwargs) ⇒ Parse::Authorization::Context

Configure the DEFAULT client's authorization context.

This is a boundary convenience, matching the shape already used for Parse::AtlasSearch.search(..., client: Parse.client): the common single-application case should not have to name the client. It is explicitly NOT the source of truth. The state lives on client.authorization, one context per client, which is what stops two named clients resolving tokens against each other's caches. To configure a secondary application, call other_client.authorization.configure(...) directly.

Returns:



439
440
441
# File 'lib/parse/authorization.rb', line 439

def configure(**kwargs)
  Parse.client.authorization.configure(**kwargs)
end

.resolve(session_token, client:) ⇒ Resolved

Resolve a session token against a specific client.

client: is required and has no default. Below the API boundary there is no such thing as "the" client, and defaulting to Parse.client here is precisely the bug this module exists to close: a token belonging to application B would be validated against application A.

Parameters:

Returns:

Raises:

  • (ArgumentError)


454
455
456
457
# File 'lib/parse/authorization.rb', line 454

def resolve(session_token, client:)
  raise ArgumentError, "Parse::Authorization.resolve requires client:" if client.nil?
  client.authorization.resolve(session_token)
end

.resolve_user(user_id, client:) ⇒ Object

Raises:

  • (ArgumentError)

See Also:



460
461
462
463
# File 'lib/parse/authorization.rb', line 460

def resolve_user(user_id, client:)
  raise ArgumentError, "Parse::Authorization.resolve_user requires client:" if client.nil?
  client.authorization.resolve_user(user_id)
end