Skip to content

get_schema_view raise exceptions.PermissionDenied() with particular urlpatterns. #6007

@yagggi

Description

@yagggi

Checklist

  • I have verified that that issue exists against the master branch of Django REST framework.
  • I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • This is not a usage question. (Those should be directed to the discussion group instead.)
  • This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Steps to reproduce

Just start a single app django project including 2 or more urls having same prefix, set a url for get_schema_view, then request the url.
Following below, I sure you will reproduce this.

the project tree:

test_proj/
├── db.sqlite3
├── manage.py
├── test_app
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── test_proj
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── venv
    ├── bin
    ├── include
    ├── lib
    └── pip-selfcheck.json

test_app/views.py:

from rest_framework import viewsets, serializers
from rest_framework.response import Response
from rest_framework.decorators import action


class TestSerializer(serializers.Serializer):
    a = serializers.IntegerField()


class TestViewSet(viewsets.GenericViewSet):
    serializer_class = TestSerializer

    @action(['GET'], detail=False)
    def test1(self):
        return Response({})

    @action(['GET'], detail=False)
    def test2(self):
        return Response({})

test_app/urls.py:

from rest_framework.routers import SimpleRouter
import test_app.views as views

router = SimpleRouter()
router.register('test', views.TestViewSet, 'test')

urlpatterns = router.urls

test_proj/urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.schemas import get_schema_view

schema_view = get_schema_view('API')

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', schema_view),
    url(r'^test', include('test_app.urls')),
]

Expected behavior

Should get a normally schema view.

Actual behavior

The get_schema_view raised exceptions.PermessionDenied()./

Further debug

I've reproduced this bug for many times, and digged into source code for a while, then I find the bug, get_schema_view func use SchemaGenerator.get_schema method, if this method return None, then the PermissionDenied will be raised.
in rest_framework.SchemaGenerator.get_schema:

links = self.get_links(None if public else request)
        if not links:
            return None

further in SchemaGenerator.get_links:

        for path, method, view in view_endpoints:
            print(path, method, view)
            if not self.has_view_permissions(path, method, view):
                continue
            link = view.schema.get_link(path, method, base_url=self.url)
            subpath = path[len(prefix):]
            keys = self.get_keys(subpath, method, view)
            insert_into(links, keys, link)

and even further in rest_framework.schemas.generators.insert_into:

def insert_into(target, keys, value):
    """
    Nested dictionary insertion.

    >>> example = {}
    >>> insert_into(example, ['a', 'b', 'c'], 123)
    >>> example
    LinkNode({'a': LinkNode({'b': LinkNode({'c': LinkNode(links=[123])}}})))
    """
    for key in keys[:-1]:
        if key not in target:
            target[key] = LinkNode()
        target = target[key]

    try:
        target.links.append((keys[-1], value))
    except TypeError:
        msg = INSERT_INTO_COLLISION_FMT.format(
            value_url=value.url,
            target_url=target.url,
            keys=keys
        )
        raise ValueError(msg)

This insert_into func does not react like it's annotation. When there's only one key, it's supposed to make target= LinkNode({'key': LinkNode(links=[value])}), but this code will make target=LinkNode(links=[('key', 'value')]), which caused not target == Ture.
By alter this insert_into func will repaire the bug, but I'm not sure if the repaire is correct, so I put what I've got here and hope this bug get further discusses, Thanks.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy