Skip to content

feat: OSS authorization foundations for enterprise RBAC#13153

Open
HimavarshaVS wants to merge 8 commits into
release-1.10.0from
feat/oss-authorization-foundations
Open

feat: OSS authorization foundations for enterprise RBAC#13153
HimavarshaVS wants to merge 8 commits into
release-1.10.0from
feat/oss-authorization-foundations

Conversation

@HimavarshaVS
Copy link
Copy Markdown
Collaborator

@HimavarshaVS HimavarshaVS commented May 15, 2026

Summary

  • Adds a pluggable AuthorizationService (BaseAuthorizationService in LFX, LangflowAuthorizationService in OSS) following the same pattern as SSO/auth plugins.
  • Introduces OSS-owned authz_* metadata tables plus casbin_rule (Alembic migration) for enterprise Casbin integration.
  • Wires flow CRUD enforcement hooks behind LANGFLOW_AUTHZ_ENABLED (default false — no behavior change for existing installs).
  • Enterprise can override authorization via lfx.services entry point authorization_service with a Casbin-backed implementation.

OSS vs Enterprise

OSS (this PR) Enterprise (follow-up)
Interface + no-op / fail-closed default Casbin enforcer + PolicySyncService
DB schema + migrations Policy CRUD, teams, shares, admin APIs
ensure_flow_permission() on flow routes SSO login → policy refresh, full route coverage
LANGFLOW_AUTHZ_ENABLED=false default Enable flag + register plugin

Configuration

LANGFLOW_AUTHZ_ENABLED=false          # default — all checks pass
LANGFLOW_AUTHZ_SUPERUSER_BYPASS=true  # when enabled, superusers allowed

When AUTHZ_ENABLED=true without an enterprise plugin, non-superuser requests are denied (fail-closed).

Test plan

  • pytest src/backend/tests/unit/services/authorization/
  • pytest src/backend/tests/unit/test_authz_models.py
  • Run make alembic-upgrade on a dev DB and verify authz_* + casbin_rule tables
  • Confirm existing flow APIs unchanged with default env (flag off)
  • Enterprise: register Casbin plugin via lfx.services and verify enforce() allows/denies per policy

Summary by CodeRabbit

Release Notes

New Features

  • Added role-based access control system for managing user permissions and resource access
  • Enabled per-flow authorization enforcement to control creation, reading, updating, and deletion of flows
  • Introduced authorization configuration settings to enable/disable authorization and control superuser privileges
  • Enhanced variable resolution with improved support for request-scoped variables and token handling

Tests

  • Added unit tests for authorization service functionality and database model validation

Review Change Stack

Resolve token/access_token and prefixed variants from LANGFLOW_REQUEST_VARIABLES and x-langflow-global-var aliases, and broaden bearer alias synthesis to support generic access-token names.
Introduce pluggable AuthorizationService, authz plugin tables, and flow
API enforcement hooks. Disabled by default (LANGFLOW_AUTHZ_ENABLED=false);
enterprise Casbin plugin can override via lfx.services entry point.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b27d4049-9e05-444d-869b-ac5dbcf98888

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces a multi-layered authorization service infrastructure spanning LFX framework and Langflow implementation, with database models, Alembic migrations, API route guards, and comprehensive tests. Additionally, it enhances LFX's variable service to support request-scoped variables and bearer token alias synthesis.

Changes

Authorization Service Infrastructure

Layer / File(s) Summary
LFX Authorization Base Contracts and Settings
src/lfx/src/lfx/services/schema.py, src/lfx/src/lfx/services/settings/auth.py, src/lfx/src/lfx/services/authorization/__init__.py, src/lfx/src/lfx/services/authorization/base.py, src/lfx/src/lfx/services/authorization/service.py
LFX defines BaseAuthorizationService abstract interface with is_enabled, enforce, and batch_enforce contracts, plus a permissive default AuthorizationService. Two new AuthSettings flags (AUTHZ_ENABLED, AUTHZ_SUPERUSER_BYPASS) control behavior.
Langflow Authorization Service Implementation
src/backend/base/langflow/services/authorization/service.py, src/backend/base/langflow/services/authorization/__init__.py, src/backend/base/langflow/services/authorization/factory.py, src/backend/base/langflow/services/schema.py, src/backend/base/langflow/services/deps.py, src/backend/base/langflow/services/utils.py
LangflowAuthorizationService implements the base interface, enforcing superuser checks when AUTHZ_ENABLED is true. Factory, dependency injection, service type enum, and registration logic wire the service into the DI container via ServiceType.AUTHORIZATION_SERVICE.
Authorization Database Models and Schemas
src/backend/base/langflow/services/database/models/auth/authz.py, src/backend/base/langflow/services/database/models/auth/__init__.py, src/backend/base/langflow/services/database/models/__init__.py
Nine SQLModel tables define Casbin rules, roles (with parent-role hierarchy), role assignments (user+role+domain uniqueness), teams, team members, shares (with scope/permission level), edit locks (per-flow), and audit logs. Enums for ShareScope and SharePermissionLevel are provided. Models are exported at package level.
Database Migration for Authorization Tables
src/backend/base/langflow/alembic/versions/f7a8b9c0d1e2_add_authz_plugin_tables.py
Alembic migration conditionally creates each authorization table (with primary keys, foreign keys using CASCADE/SET NULL, unique constraints, and indexes) in upgrade(), and drops them in downgrade() if they exist.
Authorization Utility Helpers and Route Guards
src/backend/base/langflow/services/authorization/utils.py
ensure_permission async helper builds context from user's is_superuser flag and enforces via the service; ensure_flow_permission formats flow-scoped objects (flow:{flow_id} or flow:*) and passes flow_user_id via context; permission_denied_to_http converts authorization errors to HTTP 403 responses.
Flow API Authorization Checkpoints
src/backend/base/langflow/api/v1/flows.py
Guards on create_flow, read_flow, update_flow, and delete_flow endpoints call ensure_flow_permission with the appropriate action ("create", "read", "write", "delete") to enforce per-flow access control.
Authorization Service Tests
src/backend/tests/unit/services/authorization/test_authorization_service.py, src/backend/tests/unit/test_authz_models.py
Unit tests validate LangflowAuthorizationService enforcement (disabled/enabled/superuser-bypass cases) and is_enabled() dynamic settings reflection. Integration tests persist CasbinRule, AuthzRole, and AuthzTeam models to in-memory SQLite and assert retrieval.

LFX Variable Service Enhancement

Layer / File(s) Summary
Variable Service Token Alias and Bearer Resolution
src/lfx/src/lfx/services/variable/service.py
VariableService.get_variable now prioritizes request-scoped variables parsed from LANGFLOW_REQUEST_VARIABLES JSON, then environment token aliases (e.g., token/access_token swapping), then x-langflow-global-var-* aliases, and finally synthesizes WXO <prefix>_bearer_token values from both request variables and environment. Helper methods normalize alias keys, compute token candidate names, and resolve bearer aliases.
Variable Service Tests
src/lfx/tests/unit/services/test_minimal_services.py
Adds @pytest.mark.asyncio decorators to existing async tests and introduces comprehensive tests for request-scoped variable parsing, bearer alias synthesis, fallback behavior on invalid JSON, and token alias resolution (including token/access_token equivalence and prefixed app token aliases).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 3 warnings)

Check name Status Explanation Resolution
Test Coverage For New Implementations ❌ Error Only 5 unit tests added for major authorization feature. Missing tests for ensure_permission/ensure_flow_permission utilities, factory, flow endpoints, and LFX base/default services. Add tests: (1) ensure_permission & ensure_flow_permission with routes; (2) AuthorizationServiceFactory; (3) Flow endpoint authorization blocking; (4) LFX services; (5) All authz models; (6) Migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Quality And Coverage ⚠️ Warning Test coverage is incomplete. Only 4 service tests (missing batch_enforce, get_allowed_actions), 1 model test (7 models untested), no utility function tests, and no API endpoint authorization tests. Add: tests for batch_enforce/get_allowed_actions, all authz models, ensure_permission/ensure_flow_permission utilities, and API endpoint tests with authorization enabled/disabled scenarios.
Test File Naming And Structure ⚠️ Warning Test naming contradicts intent: test_non_wxo_access_token_does_not_create_bearer_alias asserts bearer alias IS created (line 323). Ruff lint failures block merge on lowercase env vars. Rename line 323 to test_non_wxo_access_token_also_creates_bearer_alias. Fix Ruff SIM112/S105 on lines 285,290,325,330,335,340,363 by uppercasing env var names or adding noqa comments.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: OSS authorization foundations for enterprise RBAC' clearly and concisely summarizes the main feature addition: establishing authorization service foundations for OSS and enterprise RBAC integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Excessive Mock Usage Warning ✅ Passed Authorization service tested with real logic, not mocked. DB models use real in-memory database. External dependencies appropriately mocked. No excessive mocking obscuring behavior testing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oss-authorization-foundations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@HimavarshaVS HimavarshaVS changed the base branch from main to release-1.10.0 May 15, 2026 18:55
@github-actions github-actions Bot added the enhancement New feature or request label May 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

Migration Validation Passed

All migrations follow the Expand-Contract pattern correctly.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels May 15, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels May 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels May 15, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels May 15, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/base/langflow/api/v1/flows.py`:
- Around line 195-200: The handler read_public_flow incorrectly substitutes the
resolved flow owner for the caller when delegating to read_flow, causing
authorization to check the owner's role instead of the actual requester; update
read_public_flow so it calls ensure_flow_permission / read_flow using the real
caller (current_user) as the actor (do not set flow_user_id to flow_user.user_id
or overwrite current_user), or explicitly pass the caller's id as flow_user_id
when calling ensure_flow_permission/read_flow so visibility checks are based on
the requester rather than the flow owner.
- Line 43: The listed flow endpoints (PUT /flows/{flow_id}, POST /flows/batch/,
POST /flows/upload/, DELETE /flows/, POST /flows/download/) are missing the
authorization guard and thus bypass ensure_flow_permission; update each
corresponding request handler (the functions handling single flow PUT, batch
create, upload, bulk delete, and download) to require or call
ensure_flow_permission (either as a FastAPI dependency or an explicit check)
before performing any create/read/update/delete operations so AUTHZ_ENABLED=true
is fail-closed for non-superusers.

In `@src/lfx/src/lfx/services/variable/service.py`:
- Around line 32-43: The suffix check order in _token_alias_candidates is
incorrect: the endswith("_token") branch fires before the more specific
endswith("_access_token") branch, making the latter unreachable and causing
wrong alias lists; to fix, modify the _token_alias_candidates function to test
for the "_access_token" suffix before the "_token" suffix and return the correct
reciprocal candidate pairs (for names ending with "_access_token" return [name,
name without "_access_token" + "_token"], and for names ending with "_token"
return [name, name without "_token" + "_access_token"]) while keeping the
existing exact-name checks for "token" and "access_token".

In `@src/lfx/tests/unit/services/test_minimal_services.py`:
- Around line 322-330: The test function name
test_non_wxo_access_token_does_not_create_bearer_alias contradicts its body and
docstring (it sets os.environ["demo_access_token"] and asserts
variables.get_variable("demo_bearer_token") == "Bearer token-456"); rename the
function to something descriptive of the actual behavior, e.g.
test_generic_access_token_exposes_bearer_alias or
test_generic_access_token_creates_bearer_alias, and keep the body using
variables.get_variable("demo_bearer_token") and the env var "demo_access_token"
unchanged.
- Around line 282-368: The tests use intentionally-lowercased env var names
which triggers Ruff SIM112/S105; update the test fixtures to use uppercase
environment variable names for all regular vars (e.g. change
"wxo_demo_access_token" -> "WXO_DEMO_ACCESS_TOKEN", "demo_access_token" ->
"DEMO_ACCESS_TOKEN", "x-langflow-global-var-access-token" ->
"X_LANGFLOW_GLOBAL_VAR_ACCESS_TOKEN" where appropriate) and keep exercising
case-insensitive behavior via VariableService._get_wxo_bearer_alias and
variables.get_variable; for the true lowercased global header name that the
implementation expects (x-langflow-global-var-access-token), suppress the linter
locally with a per-line noqa (e.g. # noqa: SIM112) so the test intent remains
covered without failing Ruff.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c89ff2d-2557-47d6-b486-dda1d46384c1

📥 Commits

Reviewing files that changed from the base of the PR and between bd35587 and 9855a11.

📒 Files selected for processing (23)
  • src/backend/base/langflow/alembic/versions/f7a8b9c0d1e2_add_authz_plugin_tables.py
  • src/backend/base/langflow/api/v1/flows.py
  • src/backend/base/langflow/services/authorization/__init__.py
  • src/backend/base/langflow/services/authorization/factory.py
  • src/backend/base/langflow/services/authorization/service.py
  • src/backend/base/langflow/services/authorization/utils.py
  • src/backend/base/langflow/services/database/models/__init__.py
  • src/backend/base/langflow/services/database/models/auth/__init__.py
  • src/backend/base/langflow/services/database/models/auth/authz.py
  • src/backend/base/langflow/services/deps.py
  • src/backend/base/langflow/services/schema.py
  • src/backend/base/langflow/services/utils.py
  • src/backend/tests/unit/services/authorization/__init__.py
  • src/backend/tests/unit/services/authorization/test_authorization_service.py
  • src/backend/tests/unit/test_authz_models.py
  • src/lfx/src/lfx/_assets/component_index.json
  • src/lfx/src/lfx/services/authorization/__init__.py
  • src/lfx/src/lfx/services/authorization/base.py
  • src/lfx/src/lfx/services/authorization/service.py
  • src/lfx/src/lfx/services/schema.py
  • src/lfx/src/lfx/services/settings/auth.py
  • src/lfx/src/lfx/services/variable/service.py
  • src/lfx/tests/unit/services/test_minimal_services.py

from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
from langflow.initial_setup.constants import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_current_active_user
from langflow.services.authorization.utils import ensure_flow_permission
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

AuthZ is still bypassable through the unguarded flow endpoints.

Line 101, Line 195, Line 268, and Line 408 protect only four handlers. PUT /flows/{flow_id}, POST /flows/batch/, POST /flows/upload/, DELETE /flows/, and POST /flows/download/ still do flow create/read/update/delete work without ensure_flow_permission, so AUTHZ_ENABLED=true is no longer fail-closed for non-superusers.

Also applies to: 101-102, 195-200, 268-273, 408-413

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/base/langflow/api/v1/flows.py` at line 43, The listed flow
endpoints (PUT /flows/{flow_id}, POST /flows/batch/, POST /flows/upload/, DELETE
/flows/, POST /flows/download/) are missing the authorization guard and thus
bypass ensure_flow_permission; update each corresponding request handler (the
functions handling single flow PUT, batch create, upload, bulk delete, and
download) to require or call ensure_flow_permission (either as a FastAPI
dependency or an explicit check) before performing any create/read/update/delete
operations so AUTHZ_ENABLED=true is fail-closed for non-superusers.

Comment on lines +195 to +200
await ensure_flow_permission(
current_user,
"read",
flow_id=flow_id,
flow_user_id=user_flow.user_id,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

read_public_flow() now authorizes the owner, not the caller.

read_public_flow() still delegates into read_flow() after resolving the flow's owner as current_user. With this guard in place, a public flow owned by a normal user now 403s when AuthZ is enabled, while a superuser-owned public flow still passes. Public visibility should not depend on the owner's role.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/base/langflow/api/v1/flows.py` around lines 195 - 200, The
handler read_public_flow incorrectly substitutes the resolved flow owner for the
caller when delegating to read_flow, causing authorization to check the owner's
role instead of the actual requester; update read_public_flow so it calls
ensure_flow_permission / read_flow using the real caller (current_user) as the
actor (do not set flow_user_id to flow_user.user_id or overwrite current_user),
or explicitly pass the caller's id as flow_user_id when calling
ensure_flow_permission/read_flow so visibility checks are based on the requester
rather than the flow owner.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Authorize before the owner-scoped lookup.

At Line 194, Line 264, and Line 401 the flow is already resolved through current_user.id before these checks run. That makes the authorization service effectively deny-only here: an enterprise backend can reject an owned flow, but it can never grant access to a shared/team-managed flow because non-owner rows are filtered out before enforce(...) sees them.

Also applies to: 268-273, 408-413

Comment on lines +32 to +43
@staticmethod
def _token_alias_candidates(name: str) -> list[str]:
normalized = name.lower()
if normalized == "token":
return ["token", "access_token"]
if normalized == "access_token":
return ["access_token", "token"]
if normalized.endswith("_token"):
return [name, f"{name[:-len('_token')]}_access_token"]
if normalized.endswith("_access_token"):
return [name, f"{name[:-len('_access_token')]}_token"]
return [name]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: _access_token branch is unreachable — breaks PR's own _access_token resolution test.

Every string ending in _access_token also ends in _token, so the endswith("_token") check on line 39 always matches first and the _access_token branch on line 41–42 is dead code.

Concretely, for name = "my_app_access_token":

  • normalized = "my_app_access_token" → matches line 39
  • returned candidates = ["my_app_access_token", "my_app_access_access_token"]
  • "my_app_token" is not in the list

This breaks test_prefixed_token_alias_resolution_from_request_variables (lines 351–358), where {"my_app_token": "prefixed-token"} is expected to resolve a lookup of "my_app_access_token". With the current order, that test returns None and fails.

🐛 Proposed fix — swap the suffix checks so the more specific suffix is matched first
     `@staticmethod`
     def _token_alias_candidates(name: str) -> list[str]:
         normalized = name.lower()
         if normalized == "token":
             return ["token", "access_token"]
         if normalized == "access_token":
             return ["access_token", "token"]
+        if normalized.endswith("_access_token"):
+            return [name, f"{name[:-len('_access_token')]}_token"]
         if normalized.endswith("_token"):
             return [name, f"{name[:-len('_token')]}_access_token"]
-        if normalized.endswith("_access_token"):
-            return [name, f"{name[:-len('_access_token')]}_token"]
         return [name]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@staticmethod
def _token_alias_candidates(name: str) -> list[str]:
normalized = name.lower()
if normalized == "token":
return ["token", "access_token"]
if normalized == "access_token":
return ["access_token", "token"]
if normalized.endswith("_token"):
return [name, f"{name[:-len('_token')]}_access_token"]
if normalized.endswith("_access_token"):
return [name, f"{name[:-len('_access_token')]}_token"]
return [name]
`@staticmethod`
def _token_alias_candidates(name: str) -> list[str]:
normalized = name.lower()
if normalized == "token":
return ["token", "access_token"]
if normalized == "access_token":
return ["access_token", "token"]
if normalized.endswith("_access_token"):
return [name, f"{name[:-len('_access_token')]}_token"]
if normalized.endswith("_token"):
return [name, f"{name[:-len('_token')]}_access_token"]
return [name]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/src/lfx/services/variable/service.py` around lines 32 - 43, The
suffix check order in _token_alias_candidates is incorrect: the
endswith("_token") branch fires before the more specific
endswith("_access_token") branch, making the latter unreachable and causing
wrong alias lists; to fix, modify the _token_alias_candidates function to test
for the "_access_token" suffix before the "_token" suffix and return the correct
reciprocal candidate pairs (for names ending with "_access_token" return [name,
name without "_access_token" + "_token"], and for names ending with "_token"
return [name, name without "_token" + "_access_token"]) while keeping the
existing exact-name checks for "token" and "access_token".

Comment on lines +282 to +368
@pytest.mark.asyncio
async def test_get_wxo_bearer_alias_from_environment(self, variables):
"""Test that bearer aliases are synthesized from WXO access tokens."""
os.environ["wxo_demo_access_token"] = "token-123"
try:
value = await variables.get_variable("wxo_demo_bearer_token")
assert value == "Bearer token-123"
finally:
del os.environ["wxo_demo_access_token"]

@pytest.mark.asyncio
async def test_get_variable_from_langflow_request_variables(self, variables):
"""Test request-scoped variables are read from LANGFLOW_REQUEST_VARIABLES."""
os.environ["LANGFLOW_REQUEST_VARIABLES"] = '{"runtime_token":"abc123","normal_key":"value1"}'
try:
assert await variables.get_variable("runtime_token") == "abc123"
assert await variables.get_variable("normal_key") == "value1"
finally:
del os.environ["LANGFLOW_REQUEST_VARIABLES"]

@pytest.mark.asyncio
async def test_langflow_request_variables_invalid_json_falls_back(self, variables):
"""Test invalid request variable JSON does not break env fallback."""
os.environ["LANGFLOW_REQUEST_VARIABLES"] = "{not-json"
os.environ["FALLBACK_ENV_KEY"] = "fallback-value"
try:
assert await variables.get_variable("FALLBACK_ENV_KEY") == "fallback-value"
finally:
del os.environ["LANGFLOW_REQUEST_VARIABLES"]
del os.environ["FALLBACK_ENV_KEY"]

@pytest.mark.asyncio
async def test_wxo_bearer_alias_from_langflow_request_variables(self, variables):
"""Test bearer alias synthesis from request-scoped WXO access token variable."""
os.environ["LANGFLOW_REQUEST_VARIABLES"] = '{"wxo_github_access_token":"request-token"}'
try:
assert await variables.get_variable("wxo_github_bearer_token") == "Bearer request-token"
finally:
del os.environ["LANGFLOW_REQUEST_VARIABLES"]

@pytest.mark.asyncio
async def test_non_wxo_access_token_does_not_create_bearer_alias(self, variables):
"""Test that generic access token names also expose bearer aliases."""
os.environ["demo_access_token"] = "token-456"
try:
value = await variables.get_variable("demo_bearer_token")
assert value == "Bearer token-456"
finally:
del os.environ["demo_access_token"]

@pytest.mark.asyncio
async def test_bearer_alias_not_listed_when_not_set_in_memory(self, variables):
"""Test that synthesized aliases are not persisted in in-memory variable list."""
os.environ["wxo_demo_access_token"] = "token-789"
try:
assert await variables.get_variable("wxo_demo_bearer_token") == "Bearer token-789"
assert "wxo_demo_bearer_token" not in variables.list_variables()
finally:
del os.environ["wxo_demo_access_token"]

@pytest.mark.asyncio
async def test_token_access_token_alias_resolution_from_request_variables(self, variables):
"""Test token/access_token canonical alias resolution from request-scoped variables."""
os.environ["LANGFLOW_REQUEST_VARIABLES"] = '{"access_token":"request-access"}'
try:
assert await variables.get_variable("token") == "request-access"
finally:
del os.environ["LANGFLOW_REQUEST_VARIABLES"]

@pytest.mark.asyncio
async def test_prefixed_token_alias_resolution_from_request_variables(self, variables):
"""Test prefixed app token/access_token aliases are interchangeable."""
os.environ["LANGFLOW_REQUEST_VARIABLES"] = '{"my_app_token":"prefixed-token"}'
try:
assert await variables.get_variable("my_app_access_token") == "prefixed-token"
finally:
del os.environ["LANGFLOW_REQUEST_VARIABLES"]

@pytest.mark.asyncio
async def test_global_var_alias_resolution(self, variables):
"""Test x-langflow-global-var-* alias lookup."""
os.environ["x-langflow-global-var-access-token"] = "global-token"
try:
assert await variables.get_variable("access_token") == "global-token"
assert await variables.get_variable("token") == "global-token"
finally:
del os.environ["x-langflow-global-var-access-token"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CI is failing on these tests (Ruff SIM112 / S105).

The Ruff Style Check pipeline reports SIM112 ("use capitalized environment variable") on lines 285, 290, 325, 330, 335, 340, and 363, plus S105 on 285, 325, 335. This blocks merge.

The lowercase env var names appear intentional here — VariableService._get_wxo_bearer_alias lowercases os.environ keys, so the tests are exercising case-insensitive matching. Either:

  1. Switch the test fixtures to uppercase (and consider adding a separate lowercase test case to keep coverage of the case-insensitive path), or
  2. Suppress the lint rules locally with noqa since the lowercase casing is part of the test intent.
🛠 Suggested approach `#1` — use uppercase env vars (matches Ruff's expectation and real-world env conventions)
-        os.environ["wxo_demo_access_token"] = "token-123"
+        os.environ["WXO_DEMO_ACCESS_TOKEN"] = "token-123"
         try:
             value = await variables.get_variable("wxo_demo_bearer_token")
             assert value == "Bearer token-123"
         finally:
-            del os.environ["wxo_demo_access_token"]
+            del os.environ["WXO_DEMO_ACCESS_TOKEN"]

Apply the same uppercasing to demo_access_token (lines 325/330), wxo_demo_access_token (lines 335/340), and x-langflow-global-var-access-token (lines 363/368). Note: the global-alias env name is genuinely lowercased by the implementation, so for line 363 a per-line # noqa: SIM112 is the cleaner choice.

🛠 Suggested approach `#2` — keep lowercase, silence the lints
-        os.environ["wxo_demo_access_token"] = "token-123"
+        os.environ["wxo_demo_access_token"] = "token-123"  # noqa: SIM112, S105
         try:
             value = await variables.get_variable("wxo_demo_bearer_token")
             assert value == "Bearer token-123"
         finally:
-            del os.environ["wxo_demo_access_token"]
+            del os.environ["wxo_demo_access_token"]  # noqa: SIM112
🧰 Tools
🪛 GitHub Actions: Ruff Style Check / 0_Ruff Style Check (3.13).txt

[error] 285-285: ruff check failed (SIM112): Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token

🪛 GitHub Actions: Ruff Style Check / Ruff Style Check (3.13)

[error] 285-285: ruff check failed (SIM112): Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token.

🪛 GitHub Check: Ruff Style Check (3.13)

[failure] 363-363: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:363:20: SIM112 Use capitalized environment variable X-LANGFLOW-GLOBAL-VAR-ACCESS-TOKEN instead of x-langflow-global-var-access-token


[failure] 340-340: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:340:28: SIM112 Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token


[failure] 335-335: Ruff (S105)
src/lfx/tests/unit/services/test_minimal_services.py:335:47: S105 Possible hardcoded password assigned to: "wxo_demo_access_token"


[failure] 335-335: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:335:20: SIM112 Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token


[failure] 330-330: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:330:28: SIM112 Use capitalized environment variable DEMO_ACCESS_TOKEN instead of demo_access_token


[failure] 325-325: Ruff (S105)
src/lfx/tests/unit/services/test_minimal_services.py:325:43: S105 Possible hardcoded password assigned to: "demo_access_token"


[failure] 325-325: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:325:20: SIM112 Use capitalized environment variable DEMO_ACCESS_TOKEN instead of demo_access_token


[failure] 290-290: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:290:28: SIM112 Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token


[failure] 285-285: Ruff (S105)
src/lfx/tests/unit/services/test_minimal_services.py:285:47: S105 Possible hardcoded password assigned to: "wxo_demo_access_token"


[failure] 285-285: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:285:20: SIM112 Use capitalized environment variable WXO_DEMO_ACCESS_TOKEN instead of wxo_demo_access_token

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/tests/unit/services/test_minimal_services.py` around lines 282 - 368,
The tests use intentionally-lowercased env var names which triggers Ruff
SIM112/S105; update the test fixtures to use uppercase environment variable
names for all regular vars (e.g. change "wxo_demo_access_token" ->
"WXO_DEMO_ACCESS_TOKEN", "demo_access_token" -> "DEMO_ACCESS_TOKEN",
"x-langflow-global-var-access-token" -> "X_LANGFLOW_GLOBAL_VAR_ACCESS_TOKEN"
where appropriate) and keep exercising case-insensitive behavior via
VariableService._get_wxo_bearer_alias and variables.get_variable; for the true
lowercased global header name that the implementation expects
(x-langflow-global-var-access-token), suppress the linter locally with a
per-line noqa (e.g. # noqa: SIM112) so the test intent remains covered without
failing Ruff.

Comment on lines +322 to +330
@pytest.mark.asyncio
async def test_non_wxo_access_token_does_not_create_bearer_alias(self, variables):
"""Test that generic access token names also expose bearer aliases."""
os.environ["demo_access_token"] = "token-456"
try:
value = await variables.get_variable("demo_bearer_token")
assert value == "Bearer token-456"
finally:
del os.environ["demo_access_token"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test name contradicts the test body.

test_non_wxo_access_token_does_not_create_bearer_alias asserts value == "Bearer token-456" — i.e., the bearer alias is created for the generic demo_* prefix. The docstring ("Test that generic access token names also expose bearer aliases.") confirms the positive intent. Rename to match.

📝 Proposed rename
-    async def test_non_wxo_access_token_does_not_create_bearer_alias(self, variables):
+    async def test_non_wxo_access_token_also_creates_bearer_alias(self, variables):
         """Test that generic access token names also expose bearer aliases."""

As per coding guidelines: "Test files should have descriptive test function names that explain what is being tested."

🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)

[failure] 330-330: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:330:28: SIM112 Use capitalized environment variable DEMO_ACCESS_TOKEN instead of demo_access_token


[failure] 325-325: Ruff (S105)
src/lfx/tests/unit/services/test_minimal_services.py:325:43: S105 Possible hardcoded password assigned to: "demo_access_token"


[failure] 325-325: Ruff (SIM112)
src/lfx/tests/unit/services/test_minimal_services.py:325:20: SIM112 Use capitalized environment variable DEMO_ACCESS_TOKEN instead of demo_access_token

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/tests/unit/services/test_minimal_services.py` around lines 322 - 330,
The test function name test_non_wxo_access_token_does_not_create_bearer_alias
contradicts its body and docstring (it sets os.environ["demo_access_token"] and
asserts variables.get_variable("demo_bearer_token") == "Bearer token-456");
rename the function to something descriptive of the actual behavior, e.g.
test_generic_access_token_exposes_bearer_alias or
test_generic_access_token_creates_bearer_alias, and keep the body using
variables.get_variable("demo_bearer_token") and the env var "demo_access_token"
unchanged.

@github-actions
Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 38%
38.63% (48270/124934) 67.96% (6589/9694) 38.48% (1109/2882)

Unit Test Results

Tests Skipped Failures Errors Time
4323 0 💤 0 ❌ 0 🔥 9m 30s ⏱️

@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.05%. Comparing base (1e6f0f9) to head (c8dc31c).
⚠️ Report is 8 commits behind head on release-1.10.0.

❌ Your project check has failed because the head coverage (49.14%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@                 Coverage Diff                 @@
##           release-1.10.0   #13153       +/-   ##
===================================================
- Coverage           54.89%   44.05%   -10.85%     
===================================================
  Files                2148     2007      -141     
  Lines              200569   188555    -12014     
  Branches            28633    11169    -17464     
===================================================
- Hits               110097    83061    -27036     
- Misses              89288   104310    +15022     
  Partials             1184     1184               
Flag Coverage Δ
frontend 38.63% <ø> (-16.36%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/api/v1/flows.py 52.73% <ø> (ø)
src/backend/base/langflow/services/deps.py 88.60% <ø> (ø)
src/backend/base/langflow/services/schema.py 100.00% <ø> (ø)
src/backend/base/langflow/services/utils.py 83.27% <ø> (ø)
src/lfx/src/lfx/services/schema.py 100.00% <ø> (ø)
src/lfx/src/lfx/services/settings/auth.py 59.15% <ø> (ø)
src/lfx/src/lfx/services/variable/service.py 96.00% <ø> (-0.56%) ⬇️

... and 1050 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant