feat: OSS authorization foundations for enterprise RBAC#13153
feat: OSS authorization foundations for enterprise RBAC#13153HimavarshaVS wants to merge 8 commits into
Conversation
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.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis 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. ChangesAuthorization Service Infrastructure
LFX Variable Service Enhancement
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 3 warnings)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
✅ Migration Validation Passed All migrations follow the Expand-Contract pattern correctly. |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
There was a problem hiding this comment.
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
📒 Files selected for processing (23)
src/backend/base/langflow/alembic/versions/f7a8b9c0d1e2_add_authz_plugin_tables.pysrc/backend/base/langflow/api/v1/flows.pysrc/backend/base/langflow/services/authorization/__init__.pysrc/backend/base/langflow/services/authorization/factory.pysrc/backend/base/langflow/services/authorization/service.pysrc/backend/base/langflow/services/authorization/utils.pysrc/backend/base/langflow/services/database/models/__init__.pysrc/backend/base/langflow/services/database/models/auth/__init__.pysrc/backend/base/langflow/services/database/models/auth/authz.pysrc/backend/base/langflow/services/deps.pysrc/backend/base/langflow/services/schema.pysrc/backend/base/langflow/services/utils.pysrc/backend/tests/unit/services/authorization/__init__.pysrc/backend/tests/unit/services/authorization/test_authorization_service.pysrc/backend/tests/unit/test_authz_models.pysrc/lfx/src/lfx/_assets/component_index.jsonsrc/lfx/src/lfx/services/authorization/__init__.pysrc/lfx/src/lfx/services/authorization/base.pysrc/lfx/src/lfx/services/authorization/service.pysrc/lfx/src/lfx/services/schema.pysrc/lfx/src/lfx/services/settings/auth.pysrc/lfx/src/lfx/services/variable/service.pysrc/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 |
There was a problem hiding this comment.
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.
| await ensure_flow_permission( | ||
| current_user, | ||
| "read", | ||
| flow_id=flow_id, | ||
| flow_user_id=user_flow.user_id, | ||
| ) |
There was a problem hiding this comment.
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.
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
| @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] |
There was a problem hiding this comment.
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.
| @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".
| @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"] |
There was a problem hiding this comment.
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:
- Switch the test fixtures to uppercase (and consider adding a separate lowercase test case to keep coverage of the case-insensitive path), or
- Suppress the lint rules locally with
noqasince 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.
| @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"] |
There was a problem hiding this comment.
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.
Codecov Report✅ All modified and coverable lines are covered by tests. ❌ 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@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Summary
AuthorizationService(BaseAuthorizationServicein LFX,LangflowAuthorizationServicein OSS) following the same pattern as SSO/auth plugins.authz_*metadata tables pluscasbin_rule(Alembic migration) for enterprise Casbin integration.LANGFLOW_AUTHZ_ENABLED(default false — no behavior change for existing installs).lfx.servicesentry pointauthorization_servicewith a Casbin-backed implementation.OSS vs Enterprise
PolicySyncServiceensure_flow_permission()on flow routesLANGFLOW_AUTHZ_ENABLED=falsedefaultConfiguration
When
AUTHZ_ENABLED=truewithout 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.pymake alembic-upgradeon a dev DB and verifyauthz_*+casbin_ruletableslfx.servicesand verifyenforce()allows/denies per policySummary by CodeRabbit
Release Notes
New Features
Tests