I want to disable OPTIONS method on my API built with Django Rest Framework (DRF) globally (on all the API endpoints)
currently an OPTIONS call returns,
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json"
],
"name": "Login Oauth2",
"description": ""
}
which is something that I don't want someone to peek into. I want to return a blank character as github does on its API or something else.
I tried
#api_view(['POST'])
def my_method(request):
if request.method == 'OPTIONS':
return Response()
on an function based view, which returns an but inspecting the headers show,
Allow →POST, OPTIONS, OPTIONS
which has a repeated OPTIONS.
How do I achieve it? Thanks.
In your settings, add something like:
REST_FRAMEWORK = {
'DEFAULT_METADATA_CLASS': None,
}
if DEBUG:
# the default value
REST_FRAMEWORK['DEFAULT_METADATA_CLASS']: 'rest_framework.metadata.SimpleMetadata'
You can implement your own metadata class as well. Setting this to None will make it return HTTP 405 on OPTIONS requests.
Just implement a custom permission class.
your_app/permissions.py (permissions file for your app)
from rest_framework import permissions
class DisableOptionsPermission(permissions.BasePermission):
"""
Global permission to disallow all requests for method OPTIONS.
"""
def has_permission(self, request, view):
if request.method == 'OPTIONS':
return False
return True
Also set this as the default permission globally, using the DEFAULT_PERMISSION_CLASSES setting.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'your_app_name.permissions.DisableOptionsPermission',
)
}
This will disallow all incoming requests for OPTIONS method.
Related
Django 2.2, python 3.6
I'm using 2 authentication backends in my application :
GRAPHENE = {
'SCHEMA': 'my_main_app.all_schemas.schema',
'MIDDLEWARE': [
'graphql_jwt.middleware.JSONWebTokenMiddleware',
],
}
AUTHENTICATION_BACKENDS = [
'graphql_jwt.backends.JSONWebTokenBackend',
'django.contrib.auth.backends.ModelBackend',
]
I can get user info from graphql queries method like :
#login_required
def resolve_curr_user(self, info, parent_id):
user = info.context.user
But I cannot get user from a view :
def curr_user(request):
request.user # user is None
I'm passing the correct headers to the view request :
const headers = new Headers();
headers.append('Authorization', `JWT ${auth_token}`);
const init = {
method: 'GET',
headers: headers,
mode: 'cors',
cache: 'default'
};
const response = await fetch(`/curr_user_route`, init);
When authenticating for graphql query, django calls authenticate method from JSONWebTokenBackend class.
How do I tell django to call the same method for regular views ?
I am building a solution where I have one core API on which I have implemented the throttling as per the official documentation https://www.django-rest-framework.org/api-guide/throttling/. But I wonder how will I be able to monitor the requests so that no genuine user of the app gets blocked and if so should be able to unblock it.
My settings.py file
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'students': '1/minute',
}
}
And My views.py
class StudentViewSet(viewsets.ModelViewSet):
throttle_scope = 'students'
The throttle classes provided by Django REST framework do not allow you to do this. You would have to create a custom throttle class and overwrite allow_request() to log throttling events and provide some facility for whitelisting. E.g. something like this:
class WhitelistScopedRateThrottle(throttling.ScopedRateThrottle):
def allow_request(self, request, view):
allowed = super().allow_request(request, view)
if not allowed:
if self.is_whitelisted(request, view)
return True
else:
self.log_throttling(request, view)
return False
else:
return True
def is_whitelisted(self, request, view):
...
def log_throttling(self, request, view):
...
How to best implement the whitelisting and logging depends on your exact requirements.
In the Flask initalization file, I have
app = Flask("myapp")
jwt = JWT(app, AuthController.staff_login, identity)
api.add_resource(StaffLogin, '/login')
StaffLogin checks for the username / password, and will in turn call AuthController.staff_login
class AuthController():
def __init__(self):
# TODO: Create a config.yaml
self.engine = create_engine(DefaultConfig.SQLALCHEMY_DATABASE_URI)
Base.metadata.create_all(self.engine)
DBSession = sessionmaker(bind=self.engine)
self.session = DBSession()
def staff_login(self, staff_name, staff_password):
result = self.session.query(Staff) # Irrelevant details of query removed
if Staff_Authentication.check_password(staff_password, query['staff_password']):
# Build login_response as JSON
return login_response
Now I need a custom login response in this format
[
{
"projects": [
{
"project_id": 1,
"project_name": "omnipresence",
"project_root": "/path/to/project/files"
}
],
"staff_id": 13,
"staff_name": "adalovelace"
}
]
Question
How do I get Flask-JWT to use my function as authenticate?
Flask-JWT is old and has been abandoned. Check out flask-jwt-extended or flask-jwt-simple as alternatives, they are better designed and still maintained (I am the author of those extensions, so I am of course biased).
They are setup so you provide your own endpoint instead of having the extension manage an endpoint for you.
I have a backend in django, and endpoint (rest framework) to login.
simple ajax
$.ajax({
type : "POST",
url : url+login/",
data : {username:username, password:password}
})
and simple view
#api_view(['POST'])
def login(request):
username = request.POST.get('username')
password = request.POST.get('password')
user = auth.authenticate(username=username, password=password)
if user is not None:
if user.is_active:
auth.login(request, user)
#When I type here: print request.session.items()
#I get _auth_user... things.
else:
pass
return Response({})
But when I change page in my native app, and there call another ajax e.g. url "test/", and this url call this view:
def test(request):
if request.user.is_authenticated():
print "Logged in
else:
#redirect to home...
return response
Then request.user.is_authenticated return False, looks like session expire is to small, so I try this:
...
auth.login(request, user)
#When I type here: print request.session.items()
#I get _auth_user... things.
request.session.set_expire(0)
...
But this doesn't work.
** EDIT **
Im using Django Rest Framework. And 'turn on':
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
I think you should consider using
'rest_framework.authentication.TokenAuthentication'
which will be much easier to handle on a mobile app, as you can just store the token after login, and use that token for any other API call. It also allows you to safely use csrf_exempt
AFAIK, $.ajax won't send any cookies if you are crossing domains, which you are doing by definition from a mobile app. So, I think your issue has to do with CORS and how you initialize the ajax call.
Try using:
xhrFields: { withCredentials:true }
on your .ajax call.
You also need to set up CORS (use django-cors-headers)
See http://www.django-rest-framework.org/topics/ajax-csrf-cors/ for other things that you may need to worry about. In particular
https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
But as I said, consider using tokens instead of sessions.
I've had troubles with Django and post variables before.
Try changing your $.ajax call to:
$.ajax({
type : "POST",
url : "yourURL/",
contentType: "application/x-www-form-urlencoded",
data : 'username='+encodeURIComponent(username)+'&password='+encodeURIComponent(password),
});
Here is my angular service
function register(email, password, confirm_password, username){
// get works
// $http.get('/api/v1/accounts/');
return $http.post('/api/v1/accounts/',{
username: username,
password: password,
email: email
});
}
Here is my create function for my viewset
class AccountViewSet(viewsets.ModelViewSet):
lookup_field = 'username'
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get_permissions(self):
if self.request.method == 'Post':
return (permissions.AllowAny(),)
def create(self, request):
print "this is not printing at all"
Here is my urls
router = DefaultRouter()
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^$', views.index),
url(r'^api/v1/', include(router.urls)),
]
I config my angular app with csrf using this
angular
.module('main')
.run(run);
run.$inject = ['$http'];
function run($http) {
console.log("I double check this runs");
$http.defaults.xsrfHeaderName = 'X-CSRFToken';
$http.defaults.xsrfCookieName = 'csrftoken';
}
However whenever I try to post using $http I get
403 (FORBIDDEN)
While I was able to use get
Did I miss something?
Edit:
I think get_permissions is not working as intended
If I set the permission in global
from rest_framework.permissions import AllowAny
permission_classes = (AllowAny,)
the code works, however, if I use get_permissions , the code doesn't work, this is surprising as I trace through the git repo and it seems like get_permissions always gets called when checking permissions
This looks like a simple authentication or authorization (permissions) issue. I don't see any authentication permissions in your view so I assume DRF is using defaults. Anonymous users are not allowed to post by default (I don't think, anyway) django-rest-framework.org/api-guide/permissions