HumanSignal/label-studio · error · PermissionDenied

PermissionDenied

Error message

PermissionDenied

What it means

The storage view raises DRF PermissionDenied when the storage instance found by id exists but instance.has_permission(request.user) returns False, i.e. the requesting user does not own or have access to that storage object.

Source

Thrown at label_studio/io_storages/functions.py:46

        serializer_class: The serializer class to use for validation

    Returns:
        The prepared storage instance

    Raises:
        PermissionDenied: If user doesn't have permission to access the storage
        ValidationError: If serializer validation fails
    """
    if not serializer_class or not hasattr(serializer_class, 'Meta'):
        raise ValidationError('Invalid or missing serializer class')

    storage_id = request.data.get('id')
    instance = None

    if storage_id:
        instance = get_object_or_404(serializer_class.Meta.model.objects.all(), pk=storage_id)
        if not instance.has_permission(request.user):
            raise PermissionDenied()

    # combine instance fields with request.data
    serializer = serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)

    # if storage exists, we have to use instance from DB,
    # because instance from serializer won't have credentials, they were popped intentionally
    if instance:
        instance = serializer.update(instance, serializer.validated_data)
    else:
        instance = serializer_class.Meta.model(**serializer.validated_data)

    # double check: not all storages validate connection in serializer, just make another explicit check here
    try:
        instance.validate_connection()
    except Exception as exc:
        logger.error(f'Error validating storage connection: {exc}')
        raise ValidationError('Error validating storage connection')

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Omit the 'id' field to create a new storage owned by the requesting user instead of reusing someone else's
  2. Log in as, or use the API token of, the user who owns the storage (check org membership and project role)
  3. Have an admin verify has_permission in the storage model — usually requires same project/organization access
  4. Create a fresh storage under your own project and re-point your tooling at its id

Example fix

# before (id belongs to another user)
curl -H "Authorization: Token <other-user-token>" -d '{"id": 42, ...}' /api/storages/s3/
# after (omit id to create your own, or use the owner's token)
curl -H "Authorization: Token <owner-token>" -d '{"id": 42, ...}' /api/storages/s3/
Defensive patterns

Strategy: try-catch

Validate before calling

def can_access_storage(user, storage):
    return storage.has_permission(user)

Try / catch

from rest_framework.exceptions import PermissionDenied
try:
    resp = requests.post(url, json=payload, headers=auth)
    resp.raise_for_status()
except PermissionDenied:
    logger.error('Storage id %s not owned by this user — omit id or use the owner token')

Prevention

When it happens

Trigger: POSTing to a storage create/update endpoint with an 'id' belonging to another user/organization, or after a user's role changed and they no longer have access to the project that owns the storage.

Common situations: Copying API calls between accounts/teams without changing the storage id; members of an organization trying to reuse an admin-created storage; id left in the request body from a copied curl command.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/bfda03ee6de4c011. Report an issue: GitHub.