Django, TastyPie, Authentication, and custom middleware headache - django

I have a Django web application which requires authentication across the whole site. I've accomplished that with custom middleware which basically test if request.user.is_anonymous and, if they are, redirects them to the login page. It looks like this:
from django.contrib.auth.views import login
from django.contrib.auth import authenticate
from django.http import HttpResponseRedirect, HttpResponse
from django.utils import simplejson
from django.core import serializers
class SiteLogin:
"This middleware requires a login for every view"
def process_request(self, request):
if request.path != '/accounts/login/' and request.user.is_anonymous():
if request.POST:
return login(request)
else:
return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
Now I'm making an iOS app which, for now, will just do GET requests off the Django server. I am trying to use TastyPie to do this but I can't get the the authentication working. I am using ApiKeyAuthentication and, I believe, have set it up properly. However, it just redirects me to the login page. I'm wondering if I need to edit this middleware to handle TastyPie requests, but I thought TastyPie could to auth for me...
I feel like my situation is very similar to this question, but I wonder if my custom middleware is getting in the way.
Here's my api.py:
from django.contrib.auth.models import User
from django.db import models
from tastypie.resources import ModelResource
from cpm.systems.models import System
from cpm.products.models import Product
from tastypie.models import create_api_key
from tastypie.authentication import ApiKeyAuthentication
from tastypie.authorization import DjangoAuthorization, Authorization
models.signals.post_save.connect(create_api_key, sender=User)
class SystemResource(ModelResource):
class Meta:
queryset = System.objects.all()
resource_name = 'system'
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
class ProductResource(ModelResource):
class Meta:
queryset = Product.objects.all()
resource_name = 'product'
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
And a portion of my urls.py:
from cpm.ios.api import SystemResource, ProductResource
from tastypie.api import Api
v1_api = Api(api_name='v1')
v1_api.register(SystemResource())
v1_api.register(ProductResource())
admin.autodiscover()
urlpatterns = patterns('',
# iOS TastyPie related:
(r'^ios/', include(v1_api.urls)),
# .... more urls....
The URL I try to navigate to is:
http://192.168.1.130:8080/ios/v1/system/C0156/?username=garfonzo&api_key=12345?format=json
But I'm just redirected to my login page. I've followed the tutorial to a tee, created an api key on my Admin panel, and added WSGIPassAuthorization On to my apache config.
Any ideas?
EDIT I just removed that middleware altogether and now all I receive are 401 AUTHENTICATION errors...
EDIT 2
I should point out that if I remove the ?format=json then I get a response of: Sorry, not implemented yet. Please append "?format=json" to your URL.. So it's like it does authenticate, but then fails because I'm not specifying the format.
So my URL looks like this: http://192.168.1.130:8080/ios/v1/system/C0156/?username=garfonzo&api_key=12345 but as soon as I add the ?format=JSON then it goes to a 401 error.

TastyPie requests go through the same stack of middlewares as any typical django request. So, it's definitely your middleware. You have to rethink it, or just drop to the basics and use #login_required.
The api key doesn't work after you disabled the middleware because your URL is malformed. You can't use a ? in a querystring after you used it once. Try this url:
http://192.168.1.130:8080/ios/v1/system/C0156/?username=garfonzo&api_key=12345&format=JSON

Related

Django Update Middleware to replace decorator

I have the following Decorator which works fine when applied to different views with: #otp_required(login_url='login') on my site:
Decorator
from django.contrib.auth.decorators import user_passes_test
from django_otp import user_has_device
from django_otp.conf import settings
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
"""
Similar to :func:`~django.contrib.auth.decorators.login_required`, but
requires the user to be :term:`verified`. By default, this redirects users
to :setting:`OTP_LOGIN_URL`.
:param if_configured: If ``True``, an authenticated user with no confirmed
OTP devices will be allowed. Default is ``False``.
:type if_configured: bool
"""
if login_url is None:
login_url = settings.OTP_LOGIN_URL
def test(user):
return user.is_verified() or (if_configured and user.is_authenticated and not user_has_device(user))
decorator = user_passes_test(test, login_url=login_url, redirect_field_name=redirect_field_name)
return decorator if (view is None) else decorator(view)
However, I’d like to convert this into a Middleware as I want to avoid having to apply a decorator to every view on my site, but not managed to get working.
I tried to amend the following Middleware which I currently have in place which is just for authorised users and has been working but as per above decorator I want this Middleware extended to also have OTP required as well:
Middleware
from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device
from django_otp.decorators import otp_required
from django_otp.middleware import is_verified
class LoginRequiredMiddleware(MiddlewareMixin):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
##otp_required(login_url='login')
def process_request(self, request):
assert hasattr(request, 'user'), """
The Login Required middleware needs to be after AuthenticationMiddleware.
Also make sure to include the template context_processor:
'django.contrib.account.context_processors.account'."""
if not request.user.is_verified() and not request.path.startswith('/admin/') and not request.path.startswith('/account/' ):
current_route_name = resolve(request.path_info).url_name
if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
return HttpResponseRedirect(reverse(settings.LOGIN_URL))
Help is much appreciated.
The fact that you return a HttpResponseRedirect will not work: Django's MiddlewareMixin will simply call the function to (optionally) alter the request, but it will never take the return into account.
What you can do is define middleware in a decorator-like structure, and return the HttpResponseRedirect in case the user should be authenticated with:
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
def OTPRequiredMiddleware(get_response):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
def middleware(request):
from django_otp import user_has_device
if not user.is_verified() and not (if_configured and user.is_authenticated and not user_has_device(user)):
return HttpResponseRedirect(settings.OTP_LOGIN_URL)
return get_response(request)

DRF APIview with authentication

I have a class someAPI(APIView). I would like this one to be accessed by only authorised users.
I tried this link How to use login_required in django rest view
and
https://www.django-rest-framework.org/api-guide/authentication/
but none of them seem work.
My current authentication protocol is logging in with username and password. I guess a reason is because I implement this authentication with basic django (https://docs.djangoproject.com/en/3.1/topics/auth/default/) but not DRF.
from rest_framework.views import APIView
from rest_framework import authentication, permissions
from django.http import JsonResponse
class APICostAnalysisController(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
""" Initiate get API for getting cost analysis """
return JsonResponse(APICostAnalysisImpl().get(), safe=False,json_dumps_params={'indent': 2})

how can i overide django allauth signup success_url

I'm working on a project using allauth and i'm using customer user model and i wan the newly registered user to be redirected to a different page (say profile form page) which will be totally different from the login_redirect_url, I have tried it this way
any idea how i can make this work pls?
from django.shortcuts import get_object_or_404, redirect, render
from allauth.account.views import LogoutView
from django.urls import reverse_lazy
from allauth.account.views import SignupView
from django.views.generic import TemplateView
from .models import CustomUser
class Signup(SignupView):
success_url = reverse_lazy('business:company_profile')
def get_success_url(self):
return self.success_url
I am not sure there is way to override SignUp redirection since when you sign up in the application, you also sign in, which will use the login_redirect_url.
If you overrode login_redirect_url (documentation) you can update your logic and redirect the user to his profile if some fields are missing/empty?
def get_login_redirect_url(self, request):
if not request.user.your_custom_field:
path = "/accounts/{username}/"
return path.format(username=request.user.username)
else
return "/"
You could also implement another logic by adding a bool is_first_visit on your CustomerUser model (with default=True) and set it to False after his first visit.
Is the code that you proposed not working? What errors does it produce?
On first glance, the view that you've proposed should work. You would just have to make sure it's being used in "urls.py".

Django-allauth: PasswordChangeView override of success_url with logged out user results in error

When changing a password via django-allauth, the default redirect after successfully posting the password change is again the password change template. Since I find this confusing, I overrode the original PasswordChnageView in my views.py file:
from allauth.account.views import PasswordChangeView
from django.urls import reverse_lazy
class MyPasswordChangeView(PasswordChangeView):
success_url = reverse_lazy('home')
and changed my urls.py file:
from django.urls import path, include
from users.views import MyPasswordChangeView
urlpatterns = [
...
# User management
path('accounts/password/change/', MyPasswordChangeView.as_view(), name="account_change_password"),
path('accounts/', include('allauth.urls')),
...
]
This works fine when the user is logged in, however when I try to access the url http://127.0.0.1:8000/accounts/password/change/ while being logged out, I get the following error message: AttributeError at /accounts/password/change/ 'AnonymousUser' object has no attribute 'has_usable_password'
Before I created my custom override, the result of the same behaviour was that I was redirected to the login url http://127.0.0.1:8000/accounts/login/?next=/
What do I need to change with my custom view, to redirect to the login url when a logged out user tries to acces the url http://127.0.0.1:8000/accounts/password/change/
Look at the source code: The PasswordChangeView from allauth doesn't have the login required decorator in itself, that's added directly in the urls: the view used is password_change = login_required(PasswordChangeView.as_view()).
There are 2 ways:
Add login_required decorator to your URL.
from django.contrib.auth.decorators import login_required
path('accounts/password/change/', login_required(MyPasswordChangeView.as_view()), name="account_change_password"),
Inherit from LoginRequiredMixin.
from django.contrib.auth.mixins import LoginRequiredMixin
class MyPasswordChangeView(LoginRequiredMixin, PasswordChangeView):
success_url = reverse_lazy('home')
Make sure that LoginRequiredMixin is to the left most side of your child class.

rename `key` to `access_token` in django-rest-auth?

My django-rest-auth on authentication sends the following response
{"key":"XXXXXXXXXXXXXX"}
Now i am using ember-simple-auth specifically oauth2-password-grant for authentication which expects access_token in response body. After looking at source code of ember js here, i found out that the value access_token is defined as string and cannot be changed. My question is how can i implement authentication using the two stack. Is there any way that i can rename key to access_token in django ?
You can create your own MyLoginView which subclasses the original LoginView from django-rest-auth and create custom Serializer that returns the access_token field.
Something like this:
my_app/serializers.py
from rest_auth.models import TokenModel
from rest_framework import serializers
class MyTokenSerializer(serializers.ModelSerializer):
"""
Serializer for Token model.
"""
access_token = serializers.SerializerMethodField()
class Meta:
model = TokenModel
fields = ('access_token',)
def get_access_token(self, obj):
return object.key
my_app/views.py
from rest_auth.views import LoginView
from my_app.serializers import MyTokenSerializer
class MyLoginView(LoginView):
def get_response_serializer(self):
return MyTokenSerializer
urls.py
from my_app.views import MyLoginView
url(r'^login/$', MyLoginView.as_view(), name='my_login'),