I am trying to implement Token based Authorization at my server APIs. But, when I fire a query then it returns {"detail": "Authentication credentials were not provided."}
I have done almost all the settings recommended at various posts and also at the Django documentation.
In my settings.py:
INSTALLED_APPS = [
'rest_framework.authtoken',
]
And also,
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
In my views file:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
And in my Apache Config file at WSGI
WSGIPassAuthorization On
RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP-AUTHORIZATION:%1]
I don't know if I am missing anything other than this. The token is pre-generated at super user level using command line.
The default Django Rest Framework HTML that you see when you go to your website in the browser does not work with token authorization. This is because you need to put Authorization: Token <insert your token here> in your header when sending a request. This is an issue because the form you fill out has no place to change headers.
I would suggest either Postman (nice GUI), or curl (command) to test out your API since you need to edit the HTTP headers of your requests.
Have you seen this related S.O. question?
If you're using mod_wsgi, leave out the REWRITE section, put WSGIPassAuthorization On below the WSGIScriptAlias, WSGIDaemonProcess and WSGIProcessGroup directives. Just got my Apache + mod_wsgi configuration working, looks something like this:
...
WSGIScriptAlias / /var/www/html/base/wsgi.py
WSGIDaemonProcess django_app python-path=/var/www/html python-home=/var/www/html/env
WSGIProcessGroup django_app
WSGIPassAuthorization On
...
Related
I am trying to make a project and have some media files which should only be accessed by their owner.
In production, media and static files are served by apache (or nginx but I am using apache).
I was looking for some solutions and I am not able to apply.
On djangosnippets website, I found this code,
from mod_python import apache
from django.core.handlers.base import BaseHandler
from django.core.handlers.modpython import ModPythonRequest
class AccessHandler(BaseHandler):
def __call__(self, req):
from django.conf import settings
# set up middleware
if self._request_middleware is None:
self.load_middleware()
# populate the request object
request = ModPythonRequest(req)
# and apply the middleware to it
# actually only session and auth middleware would be needed here
for middleware_method in self._request_middleware:
middleware_method(request)
return request
def accesshandler(req):
os.environ.update(req.subprocess_env)
# check for PythonOptions
_str_to_bool = lambda s: s.lower() in ("1", "true", "on", "yes")
options = req.get_options()
permission_name = options.get("DjangoPermissionName", None)
staff_only = _str_to_bool(
options.get("DjangoRequireStaffStatus", "on")
)
superuser_only = _str_to_bool(
options.get("DjangoRequireSuperuserStatus", "off")
)
settings_module = options.get("DJANGO_SETTINGS_MODULE", None)
if settings_module:
os.environ["DJANGO_SETTINGS_MODULE"] = settings_module
request = AccessHandler()(req)
if request.user.is_authenticated():
if superuser_only and request.user.is_superuser:
return apache.OK
elif staff_only and request.user.is_staff:
return apache.OK
elif permission_name and request.user.has_perm(
permission_name
):
return apache.OK
return apache.HTTP_UNAUTHORIZED
But I am not able to install mod_python. Please tell me how to do that first
And I changed my .conf file for apache which is as below.
<VirtualHost *:80>
ServerName podcast.com
ServerAdmin srpatel980#gmail.com
DocumentRoot /home/username/Documents/secret_media
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias /static /home/username/Documents/secret_media/static
<Directory /home/username/Documents/secret_media/static>
Require all granted
</Directory>
Alias /media /home/username/Documents/secret_media/media
<Directory /home/username/Documents/secret_media/media>
Require all granted
</Directory>
<Directory /home/username/Documents/secret_media/secret_media>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIScriptAlias / /home/username/Documents/secret_media/secret_media/wsgi.py
WSGIDaemonProcess secret_media python-path=/home/username/Documents/secret_media python-home=/home/username/Documents/secret_media/venv
WSGIProcessGroup secret_media
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_user_module modules/mod_authz_user.so
<Location "/media/images">
PythonPath /home/username/Documents/secret_media
PythonOption DJANGO_SETTINGS_MODULE secret_media.settings
PythonAccessHandler secret_media.wsgi.py #this should point to accesshandler
SetHandler None
</Location>
</VirtualHost>
There are some settings which are repeated, don't know why, please explain
X-Sendfile
I do not know about the method you use above, but I have been using mod_xsendfile to do the same. Whats the principle: request to a url is handeled by a view that checks access rights ... the view returns a response with a key "X-Sendfile" & file ... this triggers Apache on the way back to serve the media file.
I just show you the code without testing syntax .... please ask if something is not clear
Apache httpd.conf
LoadModule xsendfile_module modules/mod_xsendfile.so
in Apache remove the usual "alias media ..."
Apache httpd-vhosts.conf
<VirtualHost *:80>
# ... all your usual Django Config staff
# remove the usual alias /media/
# Alias /media/ d:/WEBSPACES/dieweltdahinter_project/media/
XSendFile on
XSendFilePath D:/your_path/media/
<Directory "D:/your_path/media">
Order Deny,Allow
Allow from all
</Directory>
</VirtualHost>
urls.py
urlpatterns = [
.....,
re_path(r'^media/(?P<folder>[A-Za-z0-9\-_]+)/(?P<filename>[A-Za-z0-9\-_]+).(?P<extension>[A-Za-z0-9]+)\/?$', app_views.media_xsendfile, name='media-xsendfile'),
.....
]
views.py
# add decorators to manage access
#
def media_xsendfile(request, folder='', filename=None, extension=None):
# add an kind of user check ....
# only server certain type of files
if extension in ('jpg', 'png', 'gif'):
response['Content-Type'] = 'image/'+extension
elif extension == 'mp3':
response['Content-Type'] = 'audio/mpeg'
else:
return
if not folder == '':
folder = '/'+folder+'/'
response = HttpResponse()
# this is the part of the response that Apache reacts upon:
response['X-Sendfile'] = smart_str(settings.MEDIA_ROOT + folder + filename + "." + extension)
# need to handover an absolute path to your file to Apache!!
return response
One way to do it is using a so-called X-Sendfile.
In simple words:
User requests URL to get a protected file (so you need to keep your public and protected files separately, and then either proxy a request to Django for protected files, or serve files directly from the apache/nginx if they are public)
Django view decides which file to return based on URL, and checks user permission, etc.
Django returns an HTTP Response with the 'X-Sendfile' header set to the server's file path
The web server finds the file and returns it to the requester
The setup will be different for nginx and apache, according to my findings you need to install mod_xsendfile for apache, nginx supports it out of the box. Hopefully, that helps, ask any additional questions if needed.
I have a basic DRF API incorporated into to a basic Django application, hosted on Apache 2.4.
In the Apache configs, I give authorization/access to the application to anyone on our local server, with an additional basic authentication set-up (for testing) to allow an API call from an outside server to interact with some models.
When I make a GET request to the application itself with Basic Auth credentials, I get a 200 response with content (I am denied if I do not include credentials). But when I make a GET request to a DRF API endpoint, I get a 401 Unauthorized response. The 401 suggests that the response is not being authenticated.
My question is - why would (or how does) a DRF oriented API call go looking for authentication credentials in a different location than a call to the django app itself (if that's indeed what's going on)?
The Apache configuration looks like this:
DocumentRoot /var/www/html
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
WSGIDaemonProcess portal python-home=/home/bio_admin/envs/dj python-path=/var/www/portal/html/bpp
WSGIScriptAlias /portal /var/www/portal/html/bpp/bioworks/wsgi.py process-group=portal
WSGIProcessGroup portal
WSGIPassAuthorization On
Alias /static/ /var/www/portal/html/bpp/static/
<Directory /var/www/portal/html>
AuthType Basic
AuthName "portal"
AuthUserFile "/var/www/portal/html/.htpasswd"
Require user bio_admin
Require ip 74.xxx.xxx.x
</Directory>
The DRF part of settings.py file looks like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES' : (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
)
}
urls.py for the api endpoint as well as the endpoint that returns 200 with proper auth:
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path('projects/', views.ProjectsListView.as_view(), name='projects'),
path('api/projects/', views.projectListAPI.as_view())
]
urlpatterns = format_suffix_patterns(urlpatterns)
views.py class for the api in question:
class projectDetailAPI(generics.RetrieveUpdateDestroyAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
I've added this to my .htacces:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.
RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
but then trying to reach www.example.com redirects me to:
http://example.com/example/wsgi.py/
because i have WSGIScriptAlias / home/www/example.com/example/wsgi.py directive in my httpd.conf and of course i get 404 error.
Eventually, i've managed to fix this by adding next line in my urls.py:
url(r'^example/wsgi.py/$', index), (so it redirects to home page)
but i'm not quite sure that this is the right way to do it (because when i try to reach example.com i see that web browser changes address quickly to www.example.com and then again to example.com)
If anyone would ask, yes i've seen this but this didn't help me either, because browser gets url recursive problem (example.com/example.com/example.com/example.com/example.com...)
EDIT : FOLDER STRUCTURE
This is my folder structure:
\mysite\
static\
media\
.htaccess
manage.py
mysite\
templates
templatetags
tinymce
static
urls.py
settigs.py
views.py
wsgi.py
models.py
forms.py
__init__.py
I know this has been answered some time back but to add to the answer given above use
host.startswith('www.')
its more readable and also you should use permanent redirect to give the browser correct response header.
from django import http
class NoWWWRedirectMiddleware(object):
def process_request(self, request):
host = request.get_host()
if host.startswith('www.'):
if request.method == 'GET': # if wanna be a prefect REST citizen, consider HEAD and OPTIONS here as well
no_www_host = host[4:]
url = request.build_absolute_uri().replace(host, no_www_host, 1)
return http.HttpResponsePermanentRedirect(url)
I find it much simpler to accomplish no-www redirects with middleware that with with Apache mod_rewrite config.
The middleware that you linked to looks like it does the trick. I'm guessing your problems came from Apache config - make sure you remove all mod_rewrite commands (Rewrite* stuff) and then restart the apache server (ref. Apache docs but check for your OS, might be specific).
There is only one additional tweak that you should to: make sure you don't redirect any POST requests, because that might result in lost data (tangent ref: Why doesn't HTTP have POST redirect?).
Anyways, this is what I use, worked quite well for me:
from django.http import HttpResponseRedirect
class NoWWWRedirectMiddleware(object):
def process_request(self, request):
if request.method == 'GET': # if wanna be a prefect REST citizen, consider HEAD and OPTIONS here as well
host = request.get_host()
if host.lower().find('www.') == 0:
no_www_host = host[4:]
url = request.build_absolute_uri().replace(host, no_www_host, 1)
return HttpResponseRedirect(url)
To use it, put in a file somewhere, maybe mysite/mysite/middleware.py.
Then make sure it's run, in your settings.py:
MIDDLEWARE_CLASSES = (
'mysite.middleware.NoWWWRedirectMiddleware',
# ... other middleware ...
If there is no MIDDLEWARE_CLASSES in your settings.py then copy the defaults from here in the Django docs but make you're looking at the correct version of Django, there are some changes in 1.7!
Modified version that works with Django 1.11+ style middleware:
from django.http import HttpResponsePermanentRedirect
class NoWWWRedirectMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
def __call__(self, request):
response = self.process_request(request)
return response or self.get_response(request)
def process_request(self, request):
host = request.get_host()
if host.startswith('www.'):
if request.method == 'GET':
no_www = host[4:]
url = request.build_absolute_uri().replace(host, no_www, 1)
return HttpResponsePermanentRedirect(url)
This Apache configuration works for me:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC]
RewriteRule ^(.*)$ http://example.com$1 [R=301,L]
WSGIScriptAlias / /home/www/example.com/example/wsgi.py
WSGIPythonPath /home/www/example.com/example
<Directory /home/www/example.com/example>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
If my webservice (powered by Django Rest Framework, v2.3.8) is inside a location protected by Nginx's HTTP Basic Authentication, like so:
location / {
auth_basic "Restricted access";
auth_basic_user_file /path/to/htpasswd;
uwsgi_pass django;
include /etc/uwsgi/config/uwsgi_params;
}
Then, when a user authenticate and tries to access the API, the following response is obtained for all views:
{"detail": "Invalid username/password"}
Does Django Rest Framework pick up the HTTP Authorization header (meant for Nginx) even though the view requires no authentication? If so, how should I go about this?
Any help would be greatly appreciated.
By default, Django Rest Framework has two authentication classes, see here.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
)}
You can disable the rest framework authentication if you don't need it.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ()
}
Or you can remove only BasicAuthentication as it will work in your case.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication'
)}
As noted in another post, you must add a comma next to the authentication class or it can throw a TypeError.
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', #comma added here
)
Source: https://stackoverflow.com/a/22697034/5687330
Below are the relevant configuration files, also at http://dpaste.com/97213/ .
The apache config is currently working, because accessing 'example.com/' shows me the index.html file I have placed at the document root.
I'd like to serve Django/apps at the prefix '/d', so 'example.com/d/' would load the default app, 'example.com/d/app3' would load another, as configured in urls.py.
Serving Django, I'm using the suggested mod_wsgi, on Linux.
Currently, I can access the Ticket app at 'example.com/d', but when the #login_required decorator tries to send me to the login page, I get sent to 'example.com/accounts/login', rather than the expected 'example.com/d/accounts/login'.
Since the default app loads correctly, I'm not sure what I'm doing wrong here, or if this is a bug in Django when generating the urls.
Any suggestions?
EDIT:
As a note, if I change the apache config line:
WSGIScriptAlias /d /home/blah/django_projects/Tickets/apache/django.wsgi
to
WSGIScriptAlias / /home/blah/django_projects/Tickets/apache/django.wsgi
The application, commenting, and logging in all work fine. Even going to 'example.com/admin' loads the admin, although I've left the admin media broken, so no stylesheets are loaded.
--- Configs Follow:
#
# /home/blah/django_projects/Ticket/urls.py
#
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^', include('ticket.urls')),
(r'^admin/', include(admin.site.urls)),
(r'^comments/', include('django.contrib.comments.urls')),
)
#
# /home/blah/django_projects/Ticket/apache/django.wsgi
#
import os, sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/..')
sys.path.append('/home/blah/django_projects')
sys.path.append('/home/blah/django_projects/Tickets')
os.environ['DJANGO_SETTINGS_MODULE'] = 'Tickets.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
#
# /etc/apache2/sites-available/django_tickets_wsgi (apache conf)
#
NameVirtualHost *
<VirtualHost *>
Alias /media /home/blah/django_projects/Tickets/media
WSGIScriptAlias /d /home/blah/django_projects/Tickets/apache/django.wsgi
WSGIDaemonProcess exmaple_com user=blah group=blah processes=1 threads=10
WSGIProcessGroup example_com
ServerAdmin localhost#example.com
ServerName example.com
DocumentRoot /var/www/
<Directory /var/www/>
Options -Indexes FollowSymLinks -MultiViews -Includes
AllowOverride None
Order allow,deny
allow from all
</Directory>
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature Off
</VirtualHost>
This is a possible duplicate of Django Apache Redirect Problem, as that answer solved this problem.
I only eventually stumbled on that answer by opening almost all of the 'related questions' here, just out of desperation. From my perspective, I think my question has some valuable "search friendly" words.
EDIT: The answer: (via alex vasi)
Things to try:
Change current domain to "yourdomain.tld/cflow" in the "sites" framework. It's easy to do using django admin or dumpdata/loaddata manage.py commands.
Looks like your site is using login_required decorator. In that particular case you can add to settings.py:
LOGIN_URL = '/[prefix]/accounts/login/'
In your urls.py rename urlpatterns to base_urlpatterns; then add the followinig definition at the end of the same file:
urlpatterns = patterns('',
'^', include(base_urlpatterns), # iff you wish to maintain the un-prefixed URL's too
'^your_prefix/', include(base_urlpatterns),
)