-
Notifications
You must be signed in to change notification settings - Fork 50
Closed
Labels
Description
Bug Description
The enforce() method in version 1.38.0 uses the wrong parameter name when calling the Casdoor API, making it impossible to use Enforcer objects for authorization. The SDK sends enforceId but the API expects enforcerId (note
the missing 'r').
Environment
- SDK Version: 1.38.0 (latest)
- Python Version: 3.11+
- Casdoor Version: Latest (Docker)
- Installation Method:
pip install casdoor
Evidence of the Bug
Current SDK Code
File: src/casdoor/main.py (line ~267)
def enforce(
self,
permission_id: str,
model_id: str,
resource_id: str,
enforce_id: str,
owner: str,
casbin_request: Optional[List[str]] = None,
) -> bool:
url = self.endpoint + "/api/enforce"
params = {
"permissionId": permission_id,
"modelId": model_id,
"resourceId": resource_id,
"enforceId": enforce_id, # ❌ BUG: Should be "enforcerId"
"owner": owner,
}
What the API Actually Expects
Based on testing against a live Casdoor instance:
# Wrong parameter name (what SDK sends):
curl -X POST 'http://localhost:8001/api/enforce?enforceId=acme/research-approval' \
-u "client_id:client_secret" \
-H "Content-Type: application/json" \
-d '["acme/user", "resource:123", "read"]'
# Response: {"status": "error", "msg": "Missing parameter"} ❌
# Correct parameter name (what should be sent):
curl -X POST 'http://localhost:8001/api/enforce?enforcerId=acme/research-approval' \
-u "client_id:client_secret" \
-H "Content-Type: application/json" \
-d '["acme/user", "resource:123", "read"]'
# Response: {"status": "ok", "data": [true]} ✅
Impact
This bug makes the SDK completely non-functional for Enforcer-based authorization:
1. ❌ Cannot use custom Casbin enforcers via the SDK
2. ❌ Returns "Missing parameter" error
3. ❌ Forces users to bypass SDK and make raw HTTP requests
Who is affected:
- Anyone using Casdoor Enforcers (not Permissions)
- Anyone with custom Casbin models requiring fine-grained authorization
- Use cases: ABAC, ReBAC, complex RBAC with pattern matching
How to Reproduce
from casdoor import CasdoorSDK
sdk = CasdoorSDK(
endpoint="http://localhost:8001",
client_id="your-client-id",
client_secret="your-client-secret",
certificate="your-cert",
org_name="acme",
application_name="your-app"
)
# Try to check permissions using an Enforcer object
result = sdk.enforce(
permission_id="",
model_id="",
resource_id="",
enforce_id="acme/my-enforcer", # This is an Enforcer, not a Permission!
owner="",
casbin_request=["acme/user", "resource:123:facility", "approve"]
)
# Expected: Returns True or False based on enforcer policies
# Actual: Raises ValueError with "Missing parameter" error
Error output:
ValueError: Casdoor response error:
{
"status": "error",
"msg": "Missing parameter",
...
}
Root Cause Analysis
The Parameter Name Typo
The API endpoint /api/enforce accepts these mutually exclusive parameters:
- permissionId - For Permission objects ✅
- modelId - For Model objects ✅
- resourceId - For Resource objects ✅
- enforcerId - For Enforcer objects (note the 'r') ✅
- owner - For organization-level checks ✅
The SDK mistakenly uses enforceId (missing 'r'), which the API doesn't recognize.
Additional Issue: Multiple Empty Parameters
The SDK sends ALL five parameters even when most are empty strings:
# SDK sends:
?permissionId=&modelId=&resourceId=&enforceId=acme/my-enforcer&owner=
While the API documentation states:
"Only one of the parameters should be provided"
This compounds the problem, though the primary issue is the wrong parameter name.
Proposed Fix
Quick Fix (Minimal Change)
File: src/casdoor/main.py
def enforce(
self,
permission_id: str,
model_id: str,
resource_id: str,
enforce_id: str,
owner: str,
casbin_request: Optional[List[str]] = None,
) -> bool:
"""
Send data to Casdoor enforce API
:param permission_id: the permission id (i.e. organization name/permission name)
:param model_id: the model id
:param resource_id: the resource id
:param enforce_id: the enforcer id # ⬅️ Update docstring too
:param owner: the owner of the permission
:param casbin_request: a list containing the request data (i.e. sub, obj, act)
:return: a boolean value indicating whether the request is allowed
"""
url = self.endpoint + "/api/enforce"
# Build params with only non-empty values to satisfy "only one parameter" requirement
params = {}
if permission_id:
params["permissionId"] = permission_id
if model_id:
params["modelId"] = model_id
if resource_id:
params["resourceId"] = resource_id
if enforce_id:
params["enforcerId"] = enforce_id # ✅ FIXED: was "enforceId"
if owner:
params["owner"] = owner
# Validate exactly one parameter is provided
if len(params) != 1:
raise ValueError(
"Exactly one of (permission_id, model_id, resource_id, enforce_id, owner) "
"must be provided and non-empty"
)
r = requests.post(
url,
params=params,
data=json.dumps(casbin_request),
auth=(self.client_id, self.client_secret),
)
# ... rest of response handling unchanged
Same Fix Needed in Async Version
File: src/casdoor/async_main.py (same bug exists there)
async def enforce(...):
# Same fix needed here
params = {}
if permission_id:
params["permissionId"] = permission_id
if model_id:
params["modelId"] = model_id
if resource_id:
params["resourceId"] = resource_id
if enforce_id:
params["enforcerId"] = enforce_id # ✅ FIXED: was "enforceId"
if owner:
params["owner"] = owner
Alternative: Better API Design (Optional Enhancement)
For better usability, consider separate methods:
def enforce_permission(self, permission_id: str, casbin_request: List[str]) -> bool:
"""Enforce using a Permission object."""
return self._enforce({"permissionId": permission_id}, casbin_request)
def enforce_enforcer(self, enforcer_id: str, casbin_request: List[str]) -> bool:
"""Enforce using an Enforcer object."""
return self._enforce({"enforcerId": enforcer_id}, casbin_request) # ✅ Correct name
def enforce_model(self, model_id: str, casbin_request: List[str]) -> bool:
"""Enforce using a Model object."""
return self._enforce({"modelId": model_id}, casbin_request)
def _enforce(self, params: dict, casbin_request: List[str]) -> bool:
"""Internal method to call the enforce API."""
# ... implementation
This would:
- Make the API clearer (explicit about which object type you're using)
- Prevent accidental multiple parameters
- Match the conceptual model better
Workaround (For Users Affected Now)
Until this is fixed, users must bypass the SDK:
import requests
import base64
import json
# Manual HTTP request
credentials = f"{client_id}:{client_secret}"
encoded = base64.b64encode(credentials.encode()).decode()
response = requests.post(
f"{endpoint}/api/enforce?enforcerId={enforcer_id}", # ✅ Note: enforcerId
json=["user", "resource", "action"],
headers={
"Authorization": f"Basic {encoded}",
"Content-Type": "application/json"
}
)
result = response.json()
allowed = result.get("data", [False])[0] if result.get("status") == "ok" else False
Related Issues & History
- #79 - "enforce/batch_enforce API no work" (closed)
- #103 - "feat: adapt enforce & batch-enforce APIs to latest format" (merged in v1.33.0)
- This PR fixed the API format (v0/v1/v2 → casbin_request)
- But did NOT fix the parameter name typo
- #104 - Async version of #103 (merged in v1.34.0)
- casdoor/casdoor#2888 - Similar issue in Go SDK (fixed)
The parameter name typo appears to have survived the API format refactoring in #103.
Testing
After the fix, this should work:
# Test with Enforcer
sdk = CasdoorSDK(...)
result = sdk.enforce(
permission_id="",
model_id="",
resource_id="",
enforce_id="acme/my-enforcer", # Only this is set
owner="",
casbin_request=["acme/user", "resource:123", "read"]
)
assert isinstance(result, bool)
Additional Context
I discovered this while implementing an IAM authorization module that uses Casdoor Enforcers for facility-based access control. The bug blocked SDK usage entirely, requiring a complete bypass.
I'm happy to submit a PR with the fix if that would be helpful! The change is straightforward but critical for Enforcer functionality.
Thank you for maintaining this SDK! 🙏Reactions are currently unavailable