Trying to do a simple 'GET' wih admin credentials returns
"detail": "Invalid username/password."
I have a custom user model where I deleted the username, instead I use facebook_id :
USERNAME_FIELD = 'facebook_id'
I tried changing the DEFAULT_PERMISSION_CLASSES:
('rest_framework.permissions.IsAuthenticated',), -- doesn't work!
('rest_framework.permissions.IsAdminUser',), -- doesn't work!
The only one that works is:
('rest_framework.permissions.AllowAny',),
But I do not want that, since I'm building an API for a Mobile App
I also declared a CustomUserAdmin model and CustomUserCreationForm , apparently this was not the problem
Help me understand what needs to be done to fix this annoying problem, I'm guessing it might have something to do with Permissions/Authentication or the fact that I CustomUserModel..
Also, let me know if there is a better way for a mobile app client to authenticate to the api
Have just had the same problem. In my case the source of the problem was Apache's Basic Authentication, my browser was sending Authorization header and Django REST Framework thought that this header was to be handled by it. The solution is pretty simple: just remove
'rest_framework.authentication.BasicAuthentication' from your
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# ... auth classes here ...
]
}
Or explicitly set the default DEFAULT_AUTHENTICATION_CLASSES to remove BasicAuth from DRF's defaults.
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
),
}
You have the default, and then you have per view. You can set the default to IsAuthenticated, and then you override your view's particular permission_classes. e.g.
class ObtainJSONWebLogin(APIView):
permission_classes = ()
or
class Foo(viewsets.ModelViewSet):
permission_classes = ()
Related
I have a separate settings file for test environment. I'm using DRF for Authentication, but have set the default permission as rest_framework.permissions.AllowAny in the test_setting file. Now to test whether authentication is working or not, I need to override the key. But it doesn't seem to work.
I'm using the decorator #override_settings. But it doesn't seem to work. If I change the setttings file directly, then the test is passing.
#override_settings(REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
})
class AuthenticationTest(TestCase):
def test_api_should_not_be_hit_without_authorization(self):
response = client.get(reverse('some_api', kwargs={'key': value}))
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
I'm expecting the status code to 401, but it seems to pass through and is giving me 200.
I pasted the below code in the test and it shows that the REST FRAMEWORK was actually overridden, but the test is still failing.
from django.conf import settings
dir(settings)
print(settings.REST_FRAMEWORK)
EDIT:
Here is the response that I am getting:
<Response status_code=200, "application/json">
Thanks
I've had this happen before when testing DRF endpoints as an authenticated user. If the user has access to the DRF browseable API views, an API request that caused a 401 can still be represented in a valid API view.
Try adding format='json' to your client.get() to ensure that you aren't hitting the DRF interface instead of the JSON API:
response = client.get(reverse('some_api', kwargs={'key': value}), format='json')
Instead of specifying the API format in client.get(), you could change the default API request format in a test settings module so it's used in all of your test requests:
REST_FRAMEWORK = {
...
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}
I'm developing a Django Rest Framework backend for a mobile app. The API is private and will only ever be used internally.
The browsable API is convenient for helping developers working on the project but I would like to prevent anyone who's not set as an admin on the project from using the browsable interface.
I realize that the browsable admin doesn't grant any permissions that user wouldn't otherwise have, but it does have some security gray areas (e.g. for models with a foreign key relationship, the HTML selector field gets populated with all the possible related objects in the DB unless you specifically instruct it not to).
Because this app handles sensitive user data, I'd prefer to expose the smallest surface area possible to the public to reduce the risk of my own potential mistakes oversights.
Is there any way to disable the browsable API for non-admin users without disabling it for everyone? I've done a fair amount of Google searching and looked on SO and haven't found an answer. This question is close How to disable admin-style browsable interface of django-rest-framework? but not the same because those instructions disable the interface for everyone.
Is `DEFAULT_PERMISSION_CLASSES' setting not enough? This sets a default restriction on all views DRF docs on default permission classes
In settings.py:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
]
}
They will 'reach' the browsable interface but all types of requests will be denied if not authorized.
If for some reason various end-points needed to be reached by non-admin users, you could loosen the restriction on a view-by-view basis.
Assuming you're using DRF's built in views, I think you can just override get_renderers().
In your settings file:
REST_FRAMEWORK = {
# Only enable JSON renderer by default.
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
And then in your views.py:
from rest_framework import generics, renderers
class StaffBrowsableMixin(object):
def get_renderers(self):
"""
Add Browsable API renderer if user is staff.
"""
rends = self.renderer_classes
if self.request.user and self.request.user.is_staff:
rends.append(renderers.BrowsableAPIRenderer)
return [renderer() for renderer in rends]
class CustomListApiView(StaffBrowsableMixin, generics.ListAPIView):
"""
List view.
"""
# normal stuff here
In rest_framework views we have a attribute called renderes_classes
Usually we have a method get_<something> as we do with queryset/get_queryset but in this case we didn't have that, so i needed to implement a property.
from tasks.models import Task
from tasks.serializers import TaskSerializer
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.renderers import CoreJSONRenderer
class CustomRendererView:
permission_classes = (IsAuthenticatedOrReadOnly,)
#property
def renderer_classes(self):
renderers = super(ListTask, self).renderer_classes
if not self.request.user.is_staff:
renderers = [CoreJSONRenderer]
return renderers
class ListTask(CustomRendererView, ListAPIView):
queryset = Task.objects.all()
serializer_class = FullTaskSerializer
I'm trying to build a Django-rest-framework REST API that outputs JSON by default, but has XML available too.
I have read the Renderers chapter of the documentation section on default ordering, and have put this in my settings file:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework_xml.renderers.XMLRenderer',
)
}
However, this outputs XML by default. Switching the order makes no difference.
I do get JSON if I append format=json to the URL, and if I remove the XMLRenderer line altogether.
How can I set JSON to be the default?
I'm using v1.7 of Django and v3.1.1 of Django-rest-framework.
UPDATE: As requested here is the code for my views:
class CountyViewSet(viewsets.ModelViewSet):
queryset = County.objects.all()
serializer_class = CountySerializer
And the serializer:
from rest_framework import serializers
class CountySerializer(serializers.ModelSerializer):
class Meta:
model = County
fields = ('id', 'name', 'name_slug', 'ordering')
And then finally from my urls file:
router = routers.DefaultRouter()
router.register(r'county', CountyViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
my solution: file renderers.py
from rest_framework.negotiation import DefaultContentNegotiation
class IgnoreClientContentNegotiation(DefaultContentNegotiation):
logger = logging.getLogger(__name__)
def select_renderer(self, request, renderers, format_suffix):
"""
Select the first renderer in the `.renderer_classes` list.
"""
# Allow URL style format override. eg. "?format=json
format_query_param = self.settings.URL_FORMAT_OVERRIDE
format = format_suffix or request.query_params.get(format_query_param)
request.query_params.get(format_query_param), format))
if format is None:
return (renderers[0], renderers[0].media_type)
else:
return DefaultContentNegotiation.select_renderer(self, request, renderers, format_suffix)
Now just need add to settings.py in
REST_FRAMEWORK = {
(...)
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.renderers.IgnoreClientContentNegotiation',
}
Can you post your code for the actual views?
Have you defined renderer_classes in your views? as this would override the default settings.
class YourView(APIView):
renderer_classes = (XMLRenderer, JSONRenderer, )
Most likely the issue you are hitting, especially if you are testing with a browser, is that XML comes before JSON in the Accepts header. Because of this, Django REST framework is rendering XML because you specifically requested it, even though its not what you are expecting.
By giving DRF a list of default renderers, you are telling it "use these if I don't tell you to use other ones in my view". DRF will then compare the media types of these to the ones in your Accepts header to determine the best renderer to use in the response. The order doesn't matter unless you don't include a specific media type in your Accepts header, at which point it should default to the first one in the list.
Sérgio's answer is correct.
Just to add some more details for anyone in the future that comes across this.
Add djangorestframework-xml to pipfile
Update
RENDERER_CLASSES = (
'rest_framework.renderers.JSONRenderer',
'rest_framework_xml.renderers.XMLRenderer',
)
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': RENDERER_CLASSES,
}
add rest_framework_xml to INSTALLED_APPS
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_xml',
]
Follow Sérgio's advice about creating 'renderers.py'
I want to create a login api (or use an existing one if it is already pre-bundled) using django rest framework. However, I'm completely at a loss. Whenever I send a post request to the django rest framework "login" url, it just sends back the browsable api template page...
MY CONFIGURATION
urls.py
url(r'^api/v1/', include('rest_framework.urls', namespace='rest_framework'))
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
WHAT I WANT
Request:
POST /api/v1/login username='name' pass='pass'
Response:
200 OK "{username: 'name', 'userId': '54321'}" set-cookie: sessionid="blahblah"
Take a look at the api view from django-rest-framework-jwt. It's an implementation for creating auth tokens rather than cookie sessions, but your implementation will be similar. See views.py and serializers.py. You can probably use the serializers.py unchanged, and just adjust your views to return the right parameters and possibly set the session cookie (can't recall if that's already performed in authentication).
If you want something like this I do the same thing however I use Token authentication.
Check out their token page here
This may not be what you want but the way I do it is (since I'm using it as a rest api endpoints for mobile clients)
I can do my url localhost:8000/api/users/ -H Authorization : Token
A browser could then use the regular login page that you create at the provided rest framework url
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')
and to get tokens for 'login-less' navigation
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
Then if you make calls and such you can pass the authorization tokens. Of course this is just how I do it and it's probably not the most efficient way but my goal was to create a way that I can provide users with session authentication for browsers and mobile access via tokens.
Then in your views.py make sure you add the authentication requirements for that view. Almost the same as session authentication section
permission_classes = (permissions.IsAdminUser,)
but also include
authentication_classes = (authentication.TokenAuthentication,)
I hope this helps but if not, good luck on your search.
Of course token is a good way to authenticate, but questioner is asking about session authentication.
Request:
POST /api/v1/login username='username' password='password'
Put csrftoken value at X-CSRFToken in header
Even though someone using email as username filed, username name parameter is required for email input (e.g. username='sample#domain.com')
Response:
302 FOUND sessionid="blahblah"
If you not specified next value, it will automatically redirect into /accounts/profile/ which can yield 404 error
Adding our views:
from rest_framework_jwt.views import refresh_jwt_token
urlpatterns = [
...
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
...
url(r'^refresh-token/', refresh_jwt_token),
]
For the project I'm currently working on, I decided to write a custom authentication model. I'm using both the rest-framework and all-access. The local authentication is not implemented yet (it is, but in a different version, so no issue there).
The User model registers properly and OAuth authentication works. Users get logged alright.
When working with views, authentication works properly if I use django's View as base class. Not so much if I use rest-framework's APIView.
In my ProfileView (LOGIN_REDIRECT_URL redirects here) I receice a rest_framework.request.Request instance, which is OK by me. The problem is:
request.user is an instance of AnonymousUser (even after login)
I would guess the problem lies with my implementation, but then:
request._request.user is an authenticated user
(Note that request._request is the original django request)
I tend to think this is a rest-framework bug, but maybe one of you knows of a workaround?
Edit
Just to note that the login is made in a non-APIView class, but instead in a class that inherits from View. That's probably the reason the User instance is not passed to the APIView instance.
You need to set the DEFAULT_AUTHENTICATION_CLASSES in your settings.py. Else DRF will default to None as request.auth and AnonymousUser as request.user
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Once you set that and the user is logged in, ensure that the session_id is available in the request headers. Browsers typically store the session information and pass it in the headers of all requests.
If you are using a non-browser client (Eg. a mobile app), you will need to set the session id manually in the header.
Also since you mentioned you use oauth to sign-in the user, for oauth2 you need to specify the oauth2 authorization header instead of the session id.
"Authorization: Bearer <your-access-token>"
Refer to Authenticating in Django Rest Framework