Django Authorization Checking in URL having wild card pattern - django

I am trying to implement permission checking mechanism in URLs for a request using wildcard techniques, rather than implement permission checking on each views.
Currently What I have is.
urlpatterns = [
path('admin/', include('admin_urls.py')),
...
]
and my admin_urls.py is as follows
urlpatterns = [
path('', ViewSpaceIndex.as_view(), name="admin_index"),
path('', EmployeeView.as_view(), name="employee"),
...
]
and views are as follows
#method_decorator(admin_required, name='dispatch')
class EmployeeView(TemplateView):
template_name = 'secret.html'
#method_decorator(admin_required, name='dispatch')
class EmployeeView(TemplateView):
template_name = 'secret.html'
What I want to achieve is without using the repeated #method_decorator(admin_required, name='dispatch') decorator in every view I want to apply the permission to a wild
card URLs '/admin/**' with admin_required permission like in Spring boot as follows.
http.authorizeRequests()
.antMatchers("/admin/**").has_permission("is_admin")

You can do this in your project root url like this:
from .my_custom_decorators import admin_required
urlpatterns = [
path('admin/', admin_required(include('admin_urls.py'))),
...
]
I don't know this will work or not but you can try.

Related

How to write a urls.py in django so that I can do something like */page

Here is the problem:
I have an app with the following models: project, position, outreach
A position is connected to a project and project only with a Foreign key
An outreach is connected to a position and a position only with a Foreign key
I can create a new project from almost anywhere in my app (same for the other objects). Currently I wrote that a new project is created from the url dashboard/newjobproject but I would to make it so that depending on the page that I am, the url simply becomes something like www.myapp.com/..../newproject
What's a way to write the urls.py to achieve that?
from django.urls import path
from action import views
app_name = 'action'
urlpatterns = [
# ex: /action/
path('', views.login, name='login'),
path('dashboard/', views.dashboard, name='dashboard'),
path('contacts/', views.contacts, name='contacts'),
path('projects/', views.project, name='project'),
path('contacts/newcontact', views.new_contact, name='new_contact'),
path('projects/newjobproject', views.new_outreach, name='new_outreach'),
path('dashboard/newjobproject', views.new_jobproject, name='new_jobproject'),
path('projects/<uuid>/newjobposition', views.new_jobposition, name='new_jobposition'),
]
However,
Try adding this to the bottom of urlpatterns:
path('<path:p>/newjobproject', views.new_jobproject, name='whatever-name-you-want'),
and in your views.py:
def new_jobproject(request, p):
Tbh though, this is sort of a hacky way to do it. It'll break in a few locations. If you have a main urlpatterns array in which you're including the urls for this 'action' app as APIs, this solution won't work outside the API urls.
For eg. if you have:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('action.urls')),
]
And you access your url like this -> www.myapp.com/api/v1/....../newjobproject, then the code will work. If you try to access www.myapp.com/..../newjobproject or www.myapp.com/admin/..../newjobproject then the code will break and it will not match any of the paths. I'm not sure how you'd get it to work in that case.
If the above scenario is not an issue, if you're not going to be using these views as APIs, and your urlpatterns looks something like this:
urlpatterns = [
path('admin', admin.site.urls),
path('/', include('action.urls')),
]
then the code should work for all urls except for the admin/.../newjobproject case.

Why djangoRest framework retrieveAPIView not able to filter with email?

I am trying to use retrieveAPIView to look up details based on 'email'. But its throwing error:
Not Found: /apii/list/
[27/May/2021 19:53:28] "GET /apii/list/?email=%22xxx.zzz#gmail.com%22 HTTP/1.1" 404 2801
project level urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('apii/', include('AUTHENTICATION.urls')),
]
app urls.py
urlpatterns = [
path('register/', UserRegisterView.as_view(), name='register'),
path('login/', loginAPIView.as_view(), name='login'),
re_path(r'^list/(?P<email>[\w\-]+)/$', IndividualUserDetails.as_view(), name='list'),
]
views.py
class IndividualUserDetails(RetrieveAPIView):
queryset = NewEmployeeProfile.objects.all()
permission_classes = [AllowAny]
serializer_class = UserSerializer
lookup_field = 'email'
The url I am entering in postman is http://127.0.0.1:8000/apii/list/?email="xxx.zzz#gmail.com"
I am assuming I am calling the url in a wrong way may be.
Please suggest. Thank you
Your url pattern is like so:
re_path(r'^list/(?P<email>[\w\-]+)/$', IndividualUserDetails.as_view(), name='list'),
Yet you try to pass the email as a GET parameter (?email="xxx.zzz#gmail.com")and not as a part of the url itself. You need to pass the email as part of the url itself, hence your url should be like:
http://127.0.0.1:8000/apii/list/xxx.zzz#gmail.com/
Furthermore your pattern will fail in matching your email as [\w\-]+ will not match ., #, etc. Instead simply use path and the str url converter:
path(r'list/<str:email>/', IndividualUserDetails.as_view(), name='list'),

Django Rest Framework: incorrect hyperlink on second viewset of same model

I'm trying to provide two distinct APIs using DRF but I'm unable to get the second app to stop creating
hyperlinked references based on the first. It's essentially the same problem as Django Rest Framework with multiple Viewsets and Routers for the same object but I'm unable to get it working.
app1/urls.py:
router = SimpleRouter(trailing_slash=False)
router.register(prefix=r'article', viewset=app1.ArticleViewSet, basename=r'article')
urlpatterns = [path(f'', include(router.urls)]
app2/urls.py:
router = SimpleRouter(trailing_slash=False)
router.register(prefix=r'published', viewset=app2.ArticleViewSet, basename=r'published')
urlpatterns = [path(f'', include(router.urls)]
site/urls.py:
urlpatterns = [
path('app1/', include('app1.urls')),
path('app2/', include('app2.urls')),
]
While both viewsets are of the same model, the queryset & serializer for each is different.
When I GET an item from /app2/published, it has an app1 URL:
"url": "http://localhost:8000/app1/article/5461"
What I'm wanting is for items retrieved via app2 to have:
"url": "http://localhost:8000/app2/published/5461"
From looking at the docs, it appears that providing basename should do what I want, but I'm not having any luck with getting it to work.
Try the following code in your site/urls.py:
from app1.urls import router as app1_router
from app2.urls import router as app2_router
router = routers.DefaultRouter()
router.registry.extend(app1_router.registry)
router.registry.extend(app2_router.registry)
urlpatterns = [
path('', include(router.urls)), # default page to show api
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
You can see an example here, which has same structure as you need.

Is It possible to add permissions to specific url in Django

I am using IsAuthenticated permission by default and let's say I do not want to change the default permission. Is it possible to give permission of AllowAny to a specific URL?
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('user.urls')),
path('api/section/', include('section.urls')),
path('docs/', include_docs_urls(title='Great Soft Uz')) # I want this url to be public
]
Thanks in Advance
include_docs_urls function has a parameter with a default value like this
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES
def include_docs_urls(
title=None, description=None, schema_url=None, urlconf=None,
public=True, patterns=None, generator_class=SchemaGenerator,
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
renderer_classes=None):
# this is the declaration of the function
the default behavior is to extend the value of DEFAULT_PERMISSION_CLASSES from you settings but you can override it like this
from rest_framework.permissions import AllowAny
urlpatterns = [
path('docs/', include_docs_urls(title='Great Soft Uz', permission_classes=[AllowAny, ], authentication_classes=[]))
]

How to make an Custom API root list based on permissions

I wold like to personalize the api_root list based on the current user permissions, so that not all endpoints are visible to all level users.
Ex.:
router.register(r'users',views.UserViewSet, base_name='users')
router.register(r'groups', views.GroupViewSet, base_name='groups')
router.register(r'schedules', views.CallSchedulesViewSet, base_name='schedules')
urlpatterns = [
url(r'^', include(router.urls)),
...
]
For "superuser" the list should be:
users
groups
schedules
But for "normaluser" the list should only be:
schedules
The routes are registered during application starts. This does not happen on each call. So what you want may not be possible.
One things you can do is to return 404 error not found instead on 403 unauthorized error/access dened for the urls user don't have access. From user point of view that is as good as routes don't exist.
I have solved this using the following approach, maybe not the most elegant, but it serves as an example.
urls.py
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='users')
router.register(r'groups', views.GroupViewSet, base_name='groups')
router.register(r'schedules', views.SchedulesViewSet, base_name='schedules')
urlpatterns = [
url(r'^$', views.APIRoot.as_view()),
url(r'', include(router.urls)),
...
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class APIRoot(APIView):
"""
API Root ...
"""
def get(self, request):
data = {
"users": "http://localhost:8000/users/",
"groups": "http://localhost:8000/groups/",
"schedules": "http://localhost:8000/schedules/",
}
if not request.user.is_superuser:
data.pop("users")
data.pop("groups")
return Response(data)