Skip to content

Bug: enforce() uses wrong parameter "enforceId" instead of "enforcerId" - breaks Enforcer functionality #110

@Beesh1

Description

@Beesh1

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 checksThe 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/v2casbin_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! 🙏

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingreleased

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions