We're looking to implement Django OAuth on our backend in order to integrate Alexa and other 3rd party APIs. We've been following the tutorials on their site (http://django-oauth-toolkit.readthedocs.io/en/latest/tutorial/tutorial.html), but have run into a security question that has so far escaped us:
Is there a security concern that any user can access https://<oursite.com>/o/applications? If so, what steps need to be taken to prevent users from accessing these views?
The only relevant questions on SO weren't particularly helpful:
Secure creation of new applications in Django OAuth Toolkit
Disable or restrict /o/applications (django rest framework, oauth2)
I'm doing a similar thing, and I believe it is a security concern that anyone can see /o/applications - from what I can tell, that page is meant to be a development utility, not a production page. In fact, in the django-oauth-toolkit documentation, they have a code example with more restricted access to views.
from django.conf.urls import url
import oauth2_provider.views as oauth2_views
from django.conf import settings
from .views import ApiEndpoint
# OAuth2 provider endpoints
oauth2_endpoint_views = [
url(r'^authorize/$', oauth2_views.AuthorizationView.as_view(), name="authorize"),
url(r'^token/$', oauth2_views.TokenView.as_view(), name="token"),
url(r'^revoke-token/$', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
]
if settings.DEBUG:
# OAuth2 Application Management endpoints
oauth2_endpoint_views += [
url(r'^applications/$', oauth2_views.ApplicationList.as_view(), name="list"),
url(r'^applications/register/$', oauth2_views.ApplicationRegistration.as_view(), name="register"),
url(r'^applications/(?P<pk>\d+)/$', oauth2_views.ApplicationDetail.as_view(), name="detail"),
url(r'^applications/(?P<pk>\d+)/delete/$', oauth2_views.ApplicationDelete.as_view(), name="delete"),
url(r'^applications/(?P<pk>\d+)/update/$', oauth2_views.ApplicationUpdate.as_view(), name="update"),
]
# OAuth2 Token Management endpoints
oauth2_endpoint_views += [
url(r'^authorized-tokens/$', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
url(r'^authorized-tokens/(?P<pk>\d+)/delete/$', oauth2_views.AuthorizedTokenDeleteView.as_view(),
name="authorized-token-delete"),
]
urlpatterns = [
# OAuth 2 endpoints:
url(r'^o/', include(oauth2_endpoint_views, namespace="oauth2_provider")),
url(r'^admin/', include(admin.site.urls)),
url(r'^api/hello', ApiEndpoint.as_view()), # an example resource endpoint
]
The revoke token view is part of the RFC, so that one is needed. I took a similar approach in my app of only including AuthorizationView, TokenView, and RevokeTokenView.
Hope that helps!
It is a security concern, and I suggest restricting access only to superusers with active accounts as in the following code from urls.py:
from django.contrib.auth.decorators import user_passes_test
import oauth2_provider.views as oauth2_views
def is_super(user):
return user.is_superuser and user.is_active
oauth2_endpoint_views = [
url(r'^authorize/$', oauth2_views.AuthorizationView.as_view(), name="authorize"),
url(r'^token/$', oauth2_views.TokenView.as_view(), name="token"),
url(r'^revoke-token/$', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
# the above are public but we restrict the following:
url(r'^applications/$', user_passes_test(is_super)(oauth2_views.ApplicationList.as_view()), name="list"),
...
]
urlpatterns = [url(r'^o/', include(oauth2_endpoint_views, namespace="oauth2_provider"))]
To exclude 'applications/' endpoint, import just required urls instead of using whole oauth2_provider.urls:
from oauth2_provider.urls import app_name, base_urlpatterns, management_urlpatterns
urlpatterns = [
...
# oauth2
path('oauth2/', include((base_urlpatterns, app_name), namespace='oauth2_provider'))
]
Only urls, required for the client app authorization will be added:
oauth2/ ^authorize/$ [name='authorize']
oauth2/ ^token/$ [name='token']
oauth2/ ^revoke_token/$ [name='revoke-token']
oauth2/ ^introspect/$ [name='introspect']
To add/remove applications, you can either use Django admin site, or allow management_urlpatterns for admin users, as in #David Chander answer: https://stackoverflow.com/a/49210935/7709003
Related
The bounty expires in 4 days. Answers to this question are eligible for a +50 reputation bounty.
varnie wants to draw more attention to this question.
I have the following urls.py for an app named service where I register API endpoints:
from .views import AccessViewSet, CheckViewSet
app_name = "api"
router = DefaultRouter()
router.register(r"access/(?P<endpoint>.+)", AccessViewSet, basename="access")
router.register(r"check/(?P<endpoint>.+)", CheckViewSet, basename="check")
urlpatterns = [
path("", include(router.urls)),
]
Below is my project's urls.py where I use it:
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
import service.urls as service_urls
urlpatterns = [
# ...
path("service/", include('service.urls')),
]
The APIs themselves are functioning properly, but I am having trouble making them work with DRF's default API root view. The view is displaying an empty list of available endpoints. I'm not sure, but this issue may be related to the regular expressions I'm using when registering endpoints, such as r"access/(?P<endpoint>.+). If this is indeed the problem, how can I resolve it?"
DefaultRouter needs to run reverse(viewname) on your views to generate urls for the default view. It won't know about your dynamic parameter endpoint when it runs.
If it's acceptable to change your url structure to {endpoint}/access/... and {endpoint}/check/... you can:
router.register(r"access", AccessViewSet, basename="access")
router.register(r"check", CheckViewSet, basename="check")
urlpatterns = [
re_path(r"(?P<endpoint>.+)/", include(router.urls)),
]
After which, each {endpoint}/ view should have a working default API view.
If you need to preserve your url structure, you'll have to manually generate a list of available routes like this:
from collections import OrderedDict
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
class ApiRoot(APIView):
"""My API root view"""
def get(self, request, format=None):
"""List of available API endpoints"""
api_methods = [
"access-list",
"check-list",
]
endpoints = [
"endpoint-a",
"endpoint-b",
]
routes = OrderedDict()
for endpoint in endpoints:
for method in api_methods:
routes[endpoint + method] = reverse(
method, endpoint=endpoint, request=request, format=format
)
return Response(routes)
Notice the endpoint=endpoint in the reverse call.
If the code above doesn't help, please, expand your question with more details of what you're trying to archive in the first place. I have a feeling you might want to replace your viewsets with an EndpointViewSet with custom ViewSet actions for check and access.
Finally, if you're looking for routes like {endpoint_id}/access/{access_id}/... and {endpoint_id}/check/{check_id}/... check out drf-nested-routers - a drop-in replacement for DRF's Routers that supports nesting.
I'm trying to deploy gatsby based frontend with django based backend under a single domain. It will rely on Apache and mod_wsgi. In a perfect world it should work as following:
https://my-domain.com/ - gatsby frontend
https://my-domain.com/admin - django
https://my-domain.com/api - django
I can see two possibilities:
Django is aware of frontend. Serve everything via Django, setup / as a STATIC_URL.
Django is not aware of frontend. Serve /api and /admin via django. / is handled by a webserver.
I feel more comfortable with the second approach, however I do not know how to configure VirtualHost for such a scenario. The firstch approach looks like an ugly hack.
How should I proceed with that?
After compiling your gatsby project, it should be served by django as a static page.
First: The gatsby dist should be in your static_private path.
Second: In your django project, you will define a URL for / that will call an index view let's say.
Finally: in your view you should render index.html of your gatsby dist.
urls.py:
from django.contrib import admin
from django.urls import path, re_path, include
from . import views
urlpatterns = [
path('admin/', admin.site.urls),
path('apis/', include('apps.urls')),
path('/', views.index),
]
views.py:
from django.shortcuts import render
def index(request):
return render(request, 'index.html')
Note that if you are handling routing in your frontend your url pattern for the index view should be like this : re_path('^.*$', views.index)
If you are hosting your django app on heroku you will need the whitenoise middleware and set it up in your settings.py :
MIDDLEWARE = [
...
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
A doc is available here: https://devcenter.heroku.com/articles/django-assets#whitenoise
I have a large API REST project with Django Rest Framework, and I want to document it with Django REST Swagger, but I want this Swagger documentation to include only some of the endpoints of my whole project.
This is my urls.py where I set Swagger in my project:
from rest_framework_swagger.views import get_swagger_view
urlpatterns = i18n_patterns(
path('api-token-auth/', views.obtain_auth_token),
path('', include('FrontEndApp.urls')),
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('docs/',get_swagger_view(title="Intellibook API")),
path('rosetta/', include('rosetta.urls')),
path('general/', include('GeneralApp.urls')),
path('operations_manager/', include('OperationsManagerApp.urls')),
path('payments_manager/', include('PaymentsManagerApp.urls')),
#path('providers_manager/', include('ProvidersManagerApp.urls')),
path('rates_manager/', include('RatesManagerApp.urls')),
path('reports_manager/', include('ReportsManagerApp.urls')),
path('reservations_manager/', include('ReservationsManagerApp.urls')),
path('users_manager/', include('UsersManagerApp.urls')),
path('excursions_manager/', include('ExcursionsManagerApp.urls')),
path('invoices_manager/', include('InvoicesManagerApp.urls'))
)
Currently, Swagger publishes all the endpoints that are in all urls.py along the whole project. I want to set it to publish only the endpoints in ursl.py of only on of the apps of the project.
Look into
SWAGGER_SETTINGS = {
"exclude_namespaces": [], # List URL namespaces to ignore
}
Unfortunately, I couldn't find the docs. But if I get this right, you should split your urlpatterns into two parts, one for published, second for non published.
I have a Django backend that returns json data. I'm able to get data back on my localhost but got a 404 on production server. I'm running nginx in front of gunicorn server. Any ideas why I'm getting a 404? Shouldn't this be able to work to retrieve json data, or do I need to use django rest framework and implement viewsets to make this work?
Not Found
The requested URL /about was not found on this server.
urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^about', about.get_info),
]
about.py
from django.http import JsonResponse
def get_info(req):
return JsonResponse({"test": "hello"})
The problem is inside url.py. The way the rules are defined currently, it would only allow you to open about/ and admin/, i.e. with the / at the end. To fix this, you can define the URLs as following:
urlpatterns = [
url(r'^admin/$', admin.site.urls),
url(r'^about/$', about.get_info),
]
Now you should be able to use both admin/ and admin to access the page.
I am working on django project.
It utilizes multiple small apps - where one of them is used for common things (common models, forms, etc).
I want to separate whole for project to two domains, i.g.:
corporatedomain.com and userportal.com
I want corporatedomain.com to use different urls, same for userportal.com.
Is that possible? If so, how can I do this? How should I configure my urls?
Maybe you can look at the Django Site Framework. From Django official documentation:
Django comes with an optional “sites” framework. It’s a hook for associating objects and functionality to particular Web sites, and it’s a holding place for the domain names and “verbose” names of your Django-powered sites.
You can use then this approach
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.contrib.sites.models import Site
current_site = Site.objects.get_current()
if 'userportal' in current_site.domain:
urlpatterns = patterns('',
url(r'', include('userapp.urls')),
url(r'^admin/', include(admin.site.urls)),
)
else:
urlpatterns = patterns('',
url(r'', include('corporateapp.urls')),
url(r'^admin/', include(admin.site.urls)),
)
You should add as many entries as you need to Site Table and add django.contrib.sites app in your INSTALLED_APP and also a SITE_ID variable to your settings bound with the correct site ID. Use SITE_ID = 1 when no domain info are available (for example in your development session). More info about SITE_ID in this post).
In my settings I use the following approach:
SITE_ID = os.environ.get('SITE_ID', 1)
where I have set the right SITE_ID variable in each of my enrivorments.
You will have separate settings file anyway so define different ROOT_URLCONF for each domain.
UPDATE: If you don't want to use different settings then you have to write the middleware which will change the request.urlconf attribute using the HTTP_HOST header. Here is the example of such middleware.