class

DjangoAuthTagger

Inherits DjangoMiddlewareGuard < PythonGuardDefinitions < FrameworkTagger < FileHelper < Tagger < Reference < Object

Constants

ALLOW_RETURN = /\breturn[ \t]+(?!False\b|None\b|0\b)\S/
ANONYMOUS_EARLY_REFUSAL = /^[ \t]*if[ \t]+(?:not[ \t]+[\w.]*\buser\.is_authenticated|[\w.]*\buser\.is_anonymous)\b(?:[ \t]+or\b[^:\n]*)?[ \t]*:[ \t]*(?:\n[ \t]*)?(?:return[ \t]+False\b|raise\b)/m

A helper that turns an anonymous caller away before anything lets it through: if not request.user.is_authenticated: return False.

ANONYMOUS_REFUSING_CHECK = /\b(?:is_authenticated|is_active|is_staff|is_superuser|has_perms?|has_module_perms)\b/

Whatever an anonymous request.user fails: AnonymousUser is never authenticated, active, staff or superuser, and holds no permissions.

COMPOUND = /\A(?:if|elif|else|for|while|with|try|except|finally|match|case|async\s+(?:for|with))\b/
CREDENTIAL_NAME = /key|token|secret|signature|credential|password|authorization/i
CREDENTIAL_READ = /^([ \t]*)(\w*(?:key|token|secret|signature|credential|password)\w*)[ \t]*=(?!=)[^\n]*\brequest\b/im

key = self.get_key(request) (or request.headers.get(...)), then if not key: return False at the same level (not under a verb or other branch), or a top-level return request.headers.get("X-Api-Key") == settings.KEY, with no truthy return before it but one under a plain SAFE_METHODS test (only reached for safe verbs) or a return True a setting gates (the caller notes the setting): the class turns away a caller who brings no credential (Read the Docs' HasBuildAPIKey). The variable or compared field must name a credential, or the value it is compared to be a setting or the environment: anyone can send X-Requested-With: XMLHttpRequest, and a debug = request.GET.get("debug") flag decides nothing.

DECORATOR_PATTERNS = [/\@login_required/, /\@permission_required\s*\(/, /\@user_passes_test\s*\(/, /\@staff_member_required/]
DEF_LINE = /^(?:async\s+)?def\s/
DJANGO_REFUSAL = /\bHttpResponseForbidden\s*\(|\bredirect_to_login\s*\(|\bstatus\s*=\s*40[13]\b|\braise\s+(?:\w+\.)*PermissionDenied\b/

Django spells the refusal of an anonymous caller its own way too: the 403 response class, a 401/403 status, PermissionDenied, or the login redirect.

DRF_API_KEY_PERMISSION_NAME = /\A(?:HasAPIKey|BaseHasAPIKey)\z/

djangorestframework-api-key's HasAPIKey / BaseHasAPIKey refuse a request without a valid key. A project class of the same name is judged by its own definition instead.

DRF_AUTH_PERMISSION_NAME = /\A(?:IsAuthenticated|IsAdminUser|DjangoModelPermissions|DjangoObjectPermissions)\z/

DRF permissions that demand an authenticated caller for every method, and the ...OrReadOnly ones that only do for unsafe methods.

DRF_VIEW_BASE = /\w*(?:APIView|ViewSet)\b/

A DRF view: APIView / GenericAPIView / ListCreateAPIView / any ViewSet. Only these read permission_classes.

DRF_WRITE_ONLY_PERMISSION_NAME = /\A(?:IsAuthenticatedOrReadOnly|DjangoModelPermissionsOrAnonReadOnly)\z/
EARLY_ALLOW = /^[ \t]*if[ \t]+([^\n]+?):[ \t]*(?:\n[ \t]*)?return[ \t]+True\b/m

Whether permission class name refuses an anonymous caller method, as permissions_refusal reports it. A project class is judged by its has_permission (or, when that hands on to super(), its bases'), and a DjangoModelPermissions subclass by the authenticated_users_only it sets: False lets anonymous through, a setting makes it conditional.

FRAMEWORK_VIEW = /(?:View|ViewSet)\z/

A framework view whose dispatch serves the request without calling super() (Django's View and its generic views, DRF's APIView and viewsets): classes after it in the MRO never see the request.

LOGIN_NOT_REQUIRED = /\blogin_not_required\b/

Django 5.1's LoginRequiredMiddleware makes every view login-only; these opt out. The built-in login and password-reset views (and subclasses) are marked login_not_required by Django itself.

LOGIN_NOT_REQUIRED_BASE = /\b(?:LoginView|PasswordReset\w*View)\b/
LOGIN_REDIRECT = /\breturn\s+(?:redirect|HttpResponseRedirect)\s*\([^\n]*\blogin/i

Refusing an anonymous caller by sending it to the login page.

LOGIN_REQUIRED_MIDDLEWARE = "django.contrib.auth.middleware.LoginRequiredMiddleware"
METHOD_CONDITION = /\Arequest\.method\s*(==|!=|not\s+in|in)\s*(.+)\z/
METHOD_DECORATOR = /@method_decorator\s*\(([^@]*)/

@method_decorator(login_required) on a method, or @method_decorator([never_cache, login_required], name="dispatch") on the class. Its argument runs to the next decorator.

METHOD_DECORATOR_AUTH = /^\s*\[?\s*(?:[\w.]+\s*,\s*)*(login_required|permission_required|user_passes_test|staff_member_required)\b/
METHOD_DECORATOR_NAME = /\bname\s*=\s*['"](\w+)['"]/
METHOD_TEST = /^(?:if|elif)\b.*(?<![\w.])request\.method\b/
MIDDLEWARE_ASSIGNMENT = /^\s*MIDDLEWARE\s*\+?=/m
MISSING_REFUSAL = /^([ \t]*)if[ \t]+(?:not[ \t]+(\w+)|(\w+)[ \t]+is[ \t]+None)[ \t]*:[ \t]*(?:\n[ \t]*)?(?:return[ \t]+False\b|raise\b)/m
MIXIN_PATTERNS = [/LoginRequiredMixin/, /PermissionRequiredMixin/, /UserPassesTestMixin/, /StaffMemberRequiredMixin/]
NEGATED_IDENTITY = /\Anot\s+[\w.]*\b(?:is_authenticated|is_active|is_staff|is_superuser)\b|\Anot\s+[\w.]*\bhas_(?:module_)?perms?\s*\(/
NO_PERMISSION = /\bself\.handle_no_permission\s*\(/

AccessMixin.handle_no_permission sends the caller to the login page or raises PermissionDenied.

POSITIVE_IDENTITY = /\b(?:is_authenticated|is_active|is_staff|is_superuser|has_perms?|has_module_perms)\b/
REQUEST_COMPARE = /^([ \t]*)return[ \t]+request\.(?:headers|META|GET|POST|query_params|data)\b([^\n=]*)==[ \t]*([^\n]+)/m
RETURNED_HELPER = /\breturn\s+([A-Za-z_][\w.]*)\s*\(\s*request\s*[,)]/
SAFE_METHODS = ["GET", "HEAD", "OPTIONS"] of ::String
SAFE_METHODS_ALLOW = /^[ \t]*if[ \t]+([^\n]*)\bin[ \t]+(?:\w+\.)?SAFE_METHODS[ \t]*:[ \t]*(?:\n[ \t]*)?return[ \t]+(?!False\b|None\b)\S/m
SECRET_VALUE = /\A(?:settings\.|(?:os\.)?(?:environ|getenv)\b|\w*(?:key|token|secret)\w*\b)/i
SETTING_READ = /\bsettings\.([A-Z][A-Z0-9_]*)\b/
TEST_OR_DOCS_PATH = /\/(?:tests?|docs?)\/|\/(?:test_[^\/]*|[^\/]*_tests?|tests|conftest)\.py\z/

Test settings and docs configure throwaway projects.

USER_ALIAS = /\A([A-Za-z_]\w*)\s*=\s*(?:self\.)?request\.user\z/
WRITE_CALL = /\.(?:delete|save)\s*\(|\bobjects\b[^\n]*\.(?:create|update|bulk_create|bulk_update|get_or_create|update_or_create)\s*\(/

A statement that writes before the check runs, so an anonymous caller gets it done even though the response is refused.

Constructors

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

Class methods

target_techs
Source

Instance methods

perform(endpoints : Array(Endpoint)) : Array(Endpoint)

The per-endpoint shape: look at each endpoint, tag in place, hand the array back. Taggers declare only check_endpoint; those that need a pass over the whole set first (a project pre-scan, mcp's legacy prefixes) override perform and call super or loop themselves.

Source