class

RubyAuthTagger

Inherits FrameworkTagger < FileHelper < Tagger < Reference < Object

Constants

ACTION_AUTH_PATTERNS = [{/\bauthorize!/, "CanCanCan authorize!"}, {/load_and_authorize_resource/, "CanCanCan load_and_authorize_resource"}]

Pundit / CanCanCan authorization in action body. \b: not the doorkeeper_authorize! / authorize_if_got_token! an API calls.

ANONYMOUS_EXIT = /^return\b.*\s(if|unless)\s+(.+)\z/

return if <no credential> / return unless <credential>.

APP_SETTING = /\A!?\s*(?:(?:::)?[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[?!]?(?:\([^()]*\))?)+|ENV\s*(?:\[|\.fetch\b))/

A condition that reads the app's configuration rather than the request: a call on a constant (Settings::UserExperience.public, Rails.configuration.x.private_mode, Flipper.enabled?(:x)) or ENV. A deny that waits on one is a guard an operator can switch off. Current.user (CurrentAttributes) is the caller, not a setting.

BARE_CALL = /(?<![.:@\w])[a-z_]\w*[!?]?(?![\w!?]|:(?!:))/

A call with no receiver: require_login, not cookies.delete.

BEFORE_ACTION_AUTHN_PATTERNS = [{/(?<!skip_)before_action\s+:authenticate_user!/, "Devise authenticate_user!"}, {/(?<!skip_)before_action\s+:authenticate_resource_owner!/, "Doorkeeper resource_owner_authenticator"}, {/(?<!skip_)before_action\s+:authenticate_/, "Devise authentication"}, {/(?<!skip_)before_action\s+:require_login/, "require_login"}, {/(?<!skip_)before_action\s+:require_authentication/, "require_authentication"}, {/(?<!skip_)before_action\s+:check_auth/, "check_auth"}, {/(?<!skip_)before_action\s+:verify_authenticity_token/, "CSRF verify_authenticity_token"}, {/(?<!skip_)before_action\s+:doorkeeper_authorize!/, "Doorkeeper OAuth authorize"}, {/(?<!skip_)before_action\s+:authenticate_with_token/, "token authentication"}, {/(?<!skip_)before_action\s+:require_(?:authenticated_)?user!?(?!\w)/, "require_user"}]

Rails before_action authentication patterns — verify who the caller is. (?<!skip_): skip_before_action :doorkeeper_authorize! removes the callback; it must never read as registering it.

BEFORE_ACTION_AUTHZ_PATTERNS = [{/(?<!skip_)before_action\s+:authorize/, "authorize"}]

Rails before_action authorization — verify what the caller may do.

BEFORE_ACTION_PATTERNS = BEFORE_ACTION_AUTHN_PATTERNS + BEFORE_ACTION_AUTHZ_PATTERNS

Union kept for the CONTROLLER_AUTH_ANY gate and the shared walk.

BLOCK_CONDITION = /^(if|unless|elsif)\s+(.+)/
CALLBACK_REJECTS = /\b(?:head\s+(?::unauthorized|:forbidden|401|403)|status(?::|\s*=>)\s*(?::unauthorized|:forbidden|401|403)|redirect_to|redirect_back|raise|throw|halt|authenticate_or_request_with_http_\w+|request_http_\w+_authentication)\b|\w*auth\w*!/

Lines that end the request: a 401/403, a redirect, a raise, the authenticate_or_request_with_* / request_http_* helpers that answer 401 themselves, or a bang auth call.

CALLER_LOOKUP = /\b(?:User|Account|Member|Admin|Customer|Person|Identity|Principal|Session)\w*\s*\.\s*(?:find\w*|lookup|where|authenticate\w*|from_\w+)\b|\bwarden\b|\bcurrent_(?:user|account|member|admin)\b|\bauthenticate_with_http_\w+/

user = authentication_check_only: a bare call of an app method whose own body looks up the caller from where a request carries its credential (User.lookup(id: session[:user_id]), warden, HTTP auth), so the local holds the caller. current_cart reading Cart.find_by(id: session[:cart_id]) holds a cart, not the caller.

CALLER_REF = /(?<![\w.:])@?(?:user|current_user|current_account|account|account_user|user_context|pundit_user|role)\b(?!\s*:(?!:))/

A policy's reference to the caller it was built with.

CLASS_LINE = 2_u8
CLASS_SETTING = /\A(?:@?\w+\s*=\s*)?(!\s*)?self\.class\.(\w+\??)\z/

if arg = self.class.requires_login_arg: a class setting the routed controller turns on by calling the class macro that assigns it (requires_login, a def self.requires_login storing @requires_login_arg); settings are the macros it calls for this action (see RailsCallbackChain::Resolution#settings). Such a part is dropped when the setting holds the way the deny needs; nil when it does not, so the deny never fires. A setting no macro is found for stays a plain condition.

CONDITION_MODIFIER = /\s(if|unless)\s+(.+)\z/
CONTROLLER_AUTH_ANY = begin sources = [] of Regex | String SKIP_PATTERNS.each do |pattern| sources << pattern end sources << SKIP_CALLBACKS sources << Noir::RailsCallbackChain::BLOCK_CALLBACK BEFORE_ACTION_PATTERNS.each do |pattern, _| sources << pattern end HANAMI_AUTH_PATTERNS.each do |pattern, _| sources << pattern end Regex.union(sources) end

Cheap gate for check_controller_auth's backward walk, which runs to the enclosing class — or, in a Sinatra/Grape/Roda file that has none, to line 0 — for every endpoint in the file. Mechanically the union of the three pattern sets it guards, so a line it rejects cannot match any of them; it replaces 13 regex evaluations per line with one.

CONTROLLER_AUTH_LINE = 1_u8

Per-line flags for check_controller_auth, computed once per file.

The walk runs from each action back to its enclosing class, so a controller with N actions re-examined the same lines N times — a regex and an lstrip allocation per line per action. Neither depends on the action, so classify each line once: CONTROLLER_AUTH_LINE — matches CONTROLLER_AUTH_ANY; the walk runs the full pattern checks on it CLASS_LINE — any other line that opens a class; the walk stops

CREDENTIAL_PRESENT_IF = /^if\s+(?!.*(?:!|\bnot\b|\.(?:nil|blank|empty)\?)).*\b(?:request|params|cookies|session|token|header|bearer|jwt|authorization)\b/i

An if whose condition only asks whether a credential was sent: the rejection under it never fires for a request that carries none.

CREDENTIAL_PRESENT_MODIFIER = /\sif\s+@?\w*(?:token|session|header|bearer|jwt|authorization)\w*(?:\.present\?)?\z/i

doorkeeper_authorize!(*scopes) if doorkeeper_token: a statement whose trailing if is only a credential being present.

CREDENTIAL_SOURCE = /\b(?:session|cookies|request\.headers)\b|token|bearer|jwt|authoriz|api_?key/i

Where a request carries its credential.

CREDENTIAL_WORD = /token|session|bearer|jwt|authoriz|api[-_]?key|signature|hmac|secret|password|current_user|signed_in|logged_in/i

The words a request credential is spelled with.

DENY_RESPONSE = /\bhead\s*\(?\s*(?::unauthorized|:forbidden|401|403)\b|\bstatus(?::|\s*=>)\s*(?::unauthorized\b|:forbidden\b|40[13]\b)/

An explicit 401/403 answer.

DOORKEEPER_BLOCKS = {"authenticate_resource_owner!" => "resource_owner_authenticator", "authenticate_admin!" => "admin_authenticator"}

Doorkeeper's authenticators run a block from the app's config/initializers/doorkeeper.rb; current_user || redirect_to(...) rejects, User.find_by(id: session[:user_id]) alone lets anyone in.

EMPTY_LOCAL_INIT = /\A\s*=\s*(?:\[\]|\{\}|nil|false|""|Array\.new|Hash\.new)\z/
EMPTY_LOCAL_TEST = /\A(\w+)\.(blank|empty|nil|present|any)\?\z/

return false if errors.blank? where errors = [] is touched only under credential-present branches (if session[:user_id], authenticate_with_http_basic do): a caller who sent no credential leaves it empty and takes the exit, so what follows is not a login gate. Any other use of the local above the exit (errors += [..] unless x, require_credential(errors)) may fill it for an anonymous caller.

FORGERY_CHECK = /authenticity_token|csrf|forgery/i

An anti-forgery check: raise InvalidCSRFToken after return true if valid_authenticity_token?(session, params[:authenticity_token]) (zammad's verify_csrf_token). Every visitor, anonymous or not, is handed that token, so refusing a request without it is not a login gate. A deny whose own condition, or a block around it, also waits on a real credential (head :unauthorized unless current_user below a forgery exit, redirect_to x(csrf_failed: true) unless current_user) still is one; a credential word in an exit above (return if %w[token_auth].include?(@_auth_type)) lets callers out, so it is not.

GRAPE_AUTH_PATTERNS = [{/before\s+do.*authenticate/, "Grape before authenticate"}, {/before\s*\{\s*authenticate!/, "Grape authenticate!"}, {/before\s*\{\s*require_auth/, "Grape require_auth"}, {/http_basic\s+do/, "Grape http_basic auth"}, {/helpers\s+do.*authenticate/, "Grape helpers authenticate"}, {/\.error!\s*\(\s*['"]Unauthorized/, "Grape error! unauthorized"}]

Grape patterns (before blocks, http_basic, helpers)

HANAMI_AUTH_PATTERNS = HANAMI_AUTHN_PATTERNS + HANAMI_AUTHZ_PATTERNS
HANAMI_AUTHN_PATTERNS = [{/before\s+:authenticate/, "Hanami authenticate"}]

Hanami patterns

HANAMI_AUTHZ_PATTERNS = [{/before\s+:authorize/, "Hanami authorize"}]
NEGATED = /!|\bnot\b|\.(?:nil|blank|empty)\?/
NIL_ADMITS = /\A(?:return\s+true\s+unless\s+#{NIL_USER}\s*\z|return\s+true\s+if\s+(?:!\s*#{NIL_USER}\s*|#{NIL_USER}\.(?:nil|blank)\?\s*)\z)|(?:#{NIL_USER}\.(?:nil|blank)\?|!\s*#{NIL_USER}(?![\w.&?])|!\s*user_signed_in\?)\s*(?:\|\||\bor\b|\z)/
NIL_RAISES = /\braise\b.*\s(?:unless\s+#{NIL_USER}\s*\z|if\s+(?:!\s*#{NIL_USER}|#{NIL_USER}\.(?:nil|blank)\?)\s*\z)/
NIL_USER = /@?(?:current_)?(?:user|account)/

A query body line that lets a nil user pass.

NO_SETTINGS = Set(String).new
PARAMS_READ = /(?<![\w.@])params\b/

A request value read off params.

PUNDIT_ARGS = /(?<![.\w])authorize\s*\(?\s*([:@]?\w+|[A-Z][\w:]*)(?:\s*,\s*:(\w+[?!]?))?/

Pundit authorize @status, :show? asks StatusPolicy#show?; authorize Article asks the action's own query (index? for index). The verdict reads the query's definition (and the policy's initializer, up its parent policies): :refuses when a nil user gets false (or an error), :admits when one passes (true, return true unless user, user.nil? || ...), :unknown when the policy or query cannot be found. ponytail: the policy is the call's policy_class:, else found by convention (@status / Status / :status → policies/status_policy.rb); a record held in a method is unknown.

PUNDIT_ARRAY = /(?<![.\w])authorize\s*\(?\s*\[([^\[\]]*)\](?:\s*,\s*:(\w+[?!]?))?/

The namespaced form, authorize [:admin, @status], :review?.

PUNDIT_AUTHORIZE = /(?<![.\w])authorize(?:\s*\(|\s+(?=[:@A-Za-z_\[]))/

A Pundit authorize call: it raises NotAuthorizedError when the policy query answers false.

RODA_AUTHN_PATTERNS = [{/rodauth\.require_authentication/, "Rodauth require_authentication"}, {/rodauth\.require_auth/, "Rodauth require_auth"}, {/rodauth\.logged_in\?/, "Rodauth logged_in?"}, {/rodauth\.authenticated\?/, "Rodauth authenticated?"}, {/r\.halt\s+401/, "Roda 401 halt"}]

Roda / Rodauth authentication patterns

RODA_AUTHZ_PATTERNS = [{/\bauthorize!/, "Roda authorize!"}]
ROLE_CHECK = /\.(?:\w*admin|staff|moderator|superuser)\?|\b(?:can|has_role|has_permission)\?|\.role\b/

A role or permission check on the caller: current_user.admin?, can?(:x).

SECRET_SOURCE = /\bENV\b|\bcredentials\b|\bsecrets?\b|Rails\.(?:application|configuration)\b|(?:::)?[A-Z]\w*(?:::[A-Z]\w*)*\.[a-z_]\w*/

The other side of a comparison that makes a request value a credential whatever its key: a secret, the app's credentials, ENV or a config read.

SETTING_LIFTED = "a setting can make it public"

The marker denies_anonymous? leaves on a guard an app setting decides.

SINATRA_AUTH_PATTERNS = [{/before\s+do.*auth/, "Sinatra before filter auth"}, {/use\s+Rack::Auth/, "Rack::Auth middleware"}, {/use\s+Warden/, "Warden middleware"}, {/env\['warden'\]\.authenticate/, "Warden authenticate"}, {/protected!/, "protected! helper"}, {/halt\s+401/, "401 halt guard"}]

Sinatra/Rack patterns

SKIP_CALLBACKS = /\bskip_before_action\s+((?::\w+[!?]?\s*,?\s*)+)/

skip_before_action :a, :b, only: [...]: the callbacks it removes.

SKIP_PATTERNS = [/skip_before_action\s+:authenticate/, /skip_before_action\s+:require_login/, /skip_before_action\s+:require_authentication/, /^\s*allow_unauthenticated_access\b/]

skip_before_action marks public overrides

SOFT_DENY = /\bredirect_(?:to|back(?:_or_to)?)\b|(?<![\w.:])raise\b|\bhead\s*\(?\s*(?::bad_request|:not_found|4\d\d)\b|\bstatus(?::|\s*=>)\s*(?::bad_request\b|:not_found\b|4\d\d\b)/

A redirect, a raise (an app's rescue_from answers it, Discourse's raise Discourse::NotLoggedIn), or a 4xx answer other than 401/403, from a callback.

USER_PRESENT_EXIT = /\Areturn\b.*\sif\s+#{NIL_USER}(?:\.present\?)?\s*\z/

Constructors

new(options : Hash(String, YAML::Any))
Source

Class methods

target_techs
Source

Nested types