Reputation: 341
I am currently writing a REST API using Django rest framework, and oauth2 for authentication (using django-oauth-toolkit). I'm very happy with both of them, making exactly what I want.
However, I have one concern. I'm passing my app to production, and realized there might be a problem with the /o/applications/ view, which is accessible to everyone! I found myself surprised to not see anything in the doc about it, neither when I try to google it. Did I miss something?
Some ideas where to either making a custom view, requiring authentication as super-user (but this would be weird, as it would mix different kind of authentication, wouldn't it?), or add a dummy route to 401 or 403 view to /o/applications/. But these sound quite hacky to me... isn't it any official "best" solution to do it? I'd be very surprised if I'm the first one running into this issue, I must have missed something...
Thanks by advance!
Upvotes: 6
Views: 1534
Reputation: 1
I ran into the same problem and it was very frustrating. I ended up coming up with an admittedly less than ideal solution that involves re-defining the built-in views from oauth2_provider
for the routes you want protected from regular users.
oauth
and create a views.py
and urls.py
.views.py
:StaffRequiredMixin
and ApplicationOwnerIsStaffMixin
. Both require user.is_staff
to be True
.oauth2_provider.views.application.py
by copy/pasting and update them to use the new mixins.from django.contrib.auth.mixins import LoginRequiredMixin
from django.forms.models import modelform_factory
from django.urls import reverse_lazy
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
from oauth2_provider import models as oauth2_provider_models
class StaffRequiredMixin(LoginRequiredMixin):
"""Verify that the current user is staff."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class ApplicationOwnerIsStaffMixin(StaffRequiredMixin):
"""
This mixin is used to provide an Application queryset filtered by request.user.is_staff.
"""
fields = "__all__"
def get_queryset(self):
return oauth2_provider_models.get_application_model().objects.filter(user=self.request.user.is_staff)
class ApplicationRegistration(StaffRequiredMixin, CreateView):
"""
View used to register a new Application for the request.user
"""
template_name = "oauth2_provider/application_registration_form.html"
def get_form_class(self):
"""
Returns the form class for the application model
"""
return modelform_factory(
oauth2_provider_models.get_application_model(),
fields=(
"name",
"client_id",
"client_secret",
"client_type",
"authorization_grant_type",
"redirect_uris",
"algorithm",
),
)
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class ApplicationDetail(ApplicationOwnerIsStaffMixin, DetailView):
"""
Detail view for an application instance owned by the request.user
"""
context_object_name = "application"
template_name = "oauth2_provider/application_detail.html"
class ApplicationList(ApplicationOwnerIsStaffMixin, ListView):
"""
List view for all the applications owned by the request.user
"""
context_object_name = "applications"
template_name = "oauth2_provider/application_list.html"
class ApplicationDelete(ApplicationOwnerIsStaffMixin, DeleteView):
"""
View used to delete an application owned by the request.user
"""
context_object_name = "application"
success_url = reverse_lazy("oauth2_provider:list")
template_name = "oauth2_provider/application_confirm_delete.html"
class ApplicationUpdate(ApplicationOwnerIsStaffMixin, UpdateView):
"""
View used to update an application owned by the request.user
"""
context_object_name = "application"
template_name = "oauth2_provider/application_form.html"
def get_form_class(self):
"""
Returns the form class for the application model
"""
return modelform_factory(
oauth2_provider_models.get_application_model(),
fields=(
"name",
"client_id",
"client_secret",
"client_type",
"authorization_grant_type",
"redirect_uris",
"algorithm",
),
)
urls.py
:oauth2_provider.urls.py
and substitute in the new views for all the application/
and authorized_tokens/
based routes.from django.urls import re_path
from . import views
from oauth2_provider import views as oauth2_provider_views
from oauth2_provider import urls as oauth2_provider_urls
app_name = "oauth"
urlpatterns = [
# Application management views
re_path(r"^applications/$", views.ApplicationList.as_view(), name="list"),
re_path(r"^applications/register/$", views.ApplicationRegistration.as_view(), name="register"),
re_path(r"^applications/(?P<pk>[\w-]+)/$", views.ApplicationDetail.as_view(), name="detail"),
re_path(r"^applications/(?P<pk>[\w-]+)/delete/$", views.ApplicationDelete.as_view(), name="delete"),
re_path(r"^applications/(?P<pk>[\w-]+)/update/$", views.ApplicationUpdate.as_view(), name="update"),
# Token management views
re_path(r"^authorized_tokens/$", oauth2_provider_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
re_path(r"^authorized_tokens/(?P<pk>[\w-]+)/delete/$", oauth2_provider_views.AuthorizedTokenDeleteView.as_view(), name="authorized-token-delete")
]
urlpatterns += oauth2_provider_urls.base_urlpatterns
urlpatterns += oauth2_provider_urls.oidc_urlpatterns
urls.py
oauth
routes and views.django_oauth_toolkit
example, replace it with this one.urlpatterns = [
path("", ..., name="home"),
...
# Remove this one
# path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
# Add this one
path("o/", include("your_application.oauth.urls", namespace="oauth2_provider")),
...
]
Upvotes: 0
Reputation: 365
Use only base urls: authorize/
, token/
, revoke_token/
from oauth2_provider.urls import base_urlpatterns, app_name
urlpatterns = [
..., # some other urls
# oauth2 urls
path('o/', include((base_urlpatterns, app_name), namespace=app_name)
]
Instead of using all urls, as in official example:
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
Upvotes: 3
Reputation: 341
Solution found!
In fact, the reason why /o/application was accessible, is because I had a super admin session open.
Everything is great, then :)
Upvotes: 1