HTTPForbidden context route not firing - python

I'm having a bit of trouble getting security in Pyramid to work properly. My security itself seems to be working: if a user attempts to access a resource they are not authorized to view, Pyramid throws an HTTPForbidden exception. The problem is that in this instance, it's supposed to fall back to the login view, which isn't happening. I'm just getting the default Pyramid exception screen with the stacktrace.
my login view:
from pyramid.httpexceptions import HTTPForbidden
#view_config(context = HTTPForbidden, renderer="login.mak")
#view_config(route_name = 'login', renderer='login.mak')
class Login(ViewBase):
def __init__(self, request):
super(Login, self).__init__(request)
self.data['title'] = "Login"
if request.method == 'POST':
name = request.params['username']
passwd = request.params['password']
validuser = User.check(name, passwd)
if validuser is None:
self.data['requested_path'] = request.params['requestpath']
self.__call__()
else:
headers = remember(request, str(validuser.id))
raise HTTPFound(
location = request.params['requestpath'],
headers = headers
)
else:
self.data['requested_path'] = request.url
def __call__(self):
return self.data
All of views have a default permission set to 'view', my acl class looks as follows:
from pyramid.security import Allow
from pyramid.security import Everyone
from pyramid.security import Authenticated
from pyramid.security import ALL_PERMISSIONS
# Handles ACL auth for the entire application
class RootFactory(object):
__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'Editor', 'edit'),
(Allow, 'Admin', ALL_PERMISSIONS)
]
def __init__(self, request):
pass
def groupfinder(userid, request):
from ctic.models import User
user = User.get(userid)
member_groups = []
if user != None:
member_groups.append(user.group.groupname)
return member_groups
else:
return None
As I said, the ACL aspect appears to be working.
Interestingly, if I remove the default_permission from my init.py, everything works as normal.
Any pointers as to where im going wrong would be appreciated.

This is probably unrelated to your problem, but the way a class-based view works in Pyramid is that it's a 2-step process. 1) Pyramid instantiates your class with the request object 2) Pyramid calls either the __call__ method or a method specified by attr in view_config. Thus, the call to self.__call__() in your __init__ is incorrect.
The way you're using class-based views is a little unconventional, but I don't see any actual bugs in what you've pasted here.

Your groupfinder() should return a list in all cases. If the user isn't in any groups, return [] instead of None.
I'm not sure if this is your problem. But I experienced similar behaviour when I returned None instead of [].

you might want to add "permission=NO_PERMISSION_REQUIRED" to your view_config
from pyramid.security import NO_PERMISSION_REQUIRED
#view_config(context = HTTPForbidden, renderer="login.mak", permission=NO_PERMISSION_REQUIRED)

Related

auditlog with Django and DRF

I need to implement auditlog feature in one of my project which is using Django 1.8 and Django-Rest-Framework 3.2.2. I have extended BaseUserManager class to create user model since I had to use email as a username in my application ( if this information matters ).
Below is my db design which will hold logs :
**fields type desc**
id pk ( auto_increment)
cust_id FK customer
customer_name FK customer
user_id FK user
user_name FK user
module Varchar(100) sales,order,billing,etc
action Varchar(10) Create/Update/Delete
previous_value varchar(500)
current_value varchar(500)
Datetime Datetime timestamp of change
I have tried https://pypi.python.org/pypi/django-audit-log but it has 2 issues as per my requirement-
It does not capture data as per my requirement which I understand is my issue and so I modified it's code and added my fields into it's model.
It is not capturing module information. Behaviour is random.
I am seeking advice to proceed with this feature. Which package would be best suitable for my task.
P.S I have also tried Django-reversion and I have no requirement of data versioning.
Thanks
I achieved what I needed by modifying auditlog code -
Added required field in LogEntry model of auditlog.
Modified log_create,log_update,log_delete functions of receivers.py to save information in newly added fields.
Using this I am halfway done. Now only issue I am facing is that since model instance of 1 table contains information of other tables as well due to FKs used in the table.
To solve this I could come up with a solution which works well but I am not satisfied with it.
I added a function like include_in_model() in each model and modified auditlog's registry.py register() function to get those fields and only use that to save information in LogEntry model.
This approach will require me to create this include_in_model() function in each of my model class and pass required fields for particular model. This way I am avoiding FK related information.
Django Simple History is an excellent app that I've used in production projects in the past, it will give you per model Audits against your users.
Furthermore, you should create your own Authentication Class which will be responsible for logging requests. Let's assume that a User uses a Token to authenticate with your API. It gets sent in the header of each HTTP Request to your API like so: Authorization: Bearer <My Token>. We should then log the User associated with the request, the time, the user's IP and the body.
This is pretty easy:
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'common.authentication.MyTokenAuthenticationClass'
),
...
}
common/authentication.py
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions
from accounts.models import Token, AuditLog
class MyTokenAuthenticationClass(authentication.BaseAuthentication):
def authenticate(self, request):
# Grab the Athorization Header from the HTTP Request
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != b'bearer':
return None
# Check that Token header is properly formatted and present, raise errors if not
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
# Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
token.last_ip = get_real_ip(request)
token.last_login = timezone.now()
token.save()
# Add the saved token instance to the request context
request.token = token
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
# At this point, insert the Log into your AuditLog table:
AuditLog.objects.create(
user_id=token.user,
request_payload=request.body,
# Additional fields
...
)
# Return the Authenticated User associated with the Token
return (token.user, token)
Another solution would be to use django auditlog and use a custom middleware which does not capture the 'request.user' directly but at the moment when it is needed, by this time DRF will have set the correct 'request.user' so that it is no longer missing the username in the audit logs.
Create a file named (for example) auditlog_middleware.py and include it in the MIDDLEWARE in your settings.py instead of the default auditlog middleware.
from __future__ import unicode_literals
import threading
import time
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils.functional import curry
from django.apps import apps
from auditlog.models import LogEntry
from auditlog.compat import is_authenticated
# Use MiddlewareMixin when present (Django >= 1.10)
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object
threadlocal = threading.local()
class AuditlogMiddleware(MiddlewareMixin):
"""
Middleware to couple the request's user to log items. This is accomplished by currying the signal receiver with the
user from the request (or None if the user is not authenticated).
"""
def process_request(self, request):
"""
Gets the current user from the request and prepares and connects a signal receiver with the user already
attached to it.
"""
# Initialize thread local storage
threadlocal.auditlog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}
# In case of proxy, set 'original' address
if request.META.get('HTTP_X_FORWARDED_FOR'):
threadlocal.auditlog['remote_addr'] = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
# Connect signal for automatic logging
set_actor = curry(self.set_actor, request=request, signal_duid=threadlocal.auditlog['signal_duid'])
pre_save.connect(set_actor, sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'], weak=False)
def process_response(self, request, response):
"""
Disconnects the signal receiver to prevent it from staying active.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return response
def process_exception(self, request, exception):
"""
Disconnects the signal receiver to prevent it from staying active in case of an exception.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return None
#staticmethod
def set_actor(request, sender, instance, signal_duid, **kwargs):
"""
Signal receiver with an extra, required 'user' kwarg. This method becomes a real (valid) signal receiver when
it is curried with the actor.
"""
if hasattr(threadlocal, 'auditlog'):
if not hasattr(request, 'user') or not is_authenticated(request.user):
return
if signal_duid != threadlocal.auditlog['signal_duid']:
return
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
auth_user_model = apps.get_model(app_label, model_name)
except ValueError:
auth_user_model = apps.get_model('auth', 'user')
if sender == LogEntry and isinstance(request.user, auth_user_model) and instance.actor is None:
instance.actor = request.user
instance.remote_addr = threadlocal.auditlog['remote_addr']
I know that this answer is coming very late, but here it goes
Because DRF authenticates on the View level NOT on the Middleware level, the user is not yet attached to the request when AuditlogMiddleware runs, resulting in AnonymousUser
You can attach the logic from AuditlogMiddleware after your authentication
This logic connects some signals
This solution befits:
You don't have to decorate every View with it
it doesn't assume anything about AuditlogMiddleware or audit_log implementation in general. so if the code changes, this should still work
It doesn't force or duplicate DRF authentication.
#token_authentication_wrapper.py
from auditlog.middleware import AuditlogMiddleware
from rest_framework.authentication import TokenAuthentication
class TokenAuthenticationWrapper(TokenAuthentication):
def authenticate(self, request):
user, token = super().authenticate(request)
request.user = user # necessary for preventing recursion
AuditlogMiddleware().process_request(request)
return user, token
inherit from your favorite Authentication service e.g. BasicAuthentication SessionAuthentication, TokenAuthentication, etc...
and in setting.py
'DEFAULT_AUTHENTICATION_CLASSES': [
'path.to.file.token_authentication_wrapper.TokenAuthenticationWrapper',
]
First of all you can user package: https://github.com/jcugat/django-custom-user, to solve Email as Username field.
Then you can try to focus development with: http://django-reversion.readthedocs.io/en/stable/
The answer by #hassaan-alansary would have been ideal, but unfortunately the Auditlog devs made significant changes since he posted his answer, and I couldn't figure out how to reconcile their changes with Hassaan's answer.
The solution I ended up finding is based on what was shared here. Instead of writing a new DRF authentication method which invokes the middleware to do the logging, it creates a mixin which needs to be added to each of the DRF views you want added to the audit log. The solution below is the modified version of the one I ended up using from the link above.
# mixins.py
import threading
import time
from functools import partial
from django.db.models.signals import pre_save
from auditlog.models import LogEntry
threadlocal = threading.local()
class DRFDjangoAuditModelMixin:
"""
Mixin to integrate django-auditlog with Django Rest Framework.
This is needed because DRF does not perform the authentication at middleware layer
instead it performs the authentication at View layer.
This mixin adds behavior to connect/disconnect the signals needed by django-auditlog to auto
log changes on models.
It assumes that AuditlogMiddleware is activated in settings.MIDDLEWARE_CLASSES
"""
#staticmethod
def _set_actor(user, sender, instance, signal_duid, **kwargs):
# This is a reimplementation of auditlog.context._set_actor.
# Unfortunately the original logic cannot be used, because
# there is a type mismatch between user and auth_user_model.
if signal_duid != threadlocal.auditlog["signal_duid"]:
return
if (
sender == LogEntry
#and isinstance(user, auth_user_model)
and instance.actor is None
):
instance.actor = user
instance.remote_addr = threadlocal.auditlog["remote_addr"]
def initial(self, request, *args, **kwargs):
"""Overwritten to use django-auditlog if needed."""
super().initial(request, *args, **kwargs)
remote_addr = AuditlogMiddleware._get_remote_addr(request)
actor = request.user
set_actor = partial(
self._set_actor,
user=actor,
signal_duid=threadlocal.auditlog["signal_duid"],
)
pre_save.connect(
set_actor,
sender=LogEntry,
dispatch_uid=threadlocal.auditlog["signal_duid"],
weak=False,
)
def finalize_response(self, request, response, *args, **kwargs):
"""Overwritten to cleanup django-auditlog if needed."""
response = super().finalize_response(request, response, *args, **kwargs)
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
del threadlocal.auditlog
return response
You then need to add this mixin to each of your views:
# views.py
...
class CustomerViewSet(DRFDjangoAuditModelMixin, ModelViewSet):
queryset = Client.objects.all()
serializer = ClientSerializer
....
The down side of this implementation is that it isn't DRY on a couple of levels. Not only do you need to add the mixin to each DRF view, but it copies code from nearly all the logging behaviour of auditlog, particularly private methods. I therefore expect this solution to either need adjustment in the future, or for it to also become obsolete.
The solution above is based on this revision of auditlog.

Flask RESTful API and authenticating for a specific user

I am relatively new to RESTful API, so it is certainly possible I am not designing this correctly.
I want to return a different subsets of a JSON user object from /api/users/[user_id] based on who is authenticating. So if the user "alice" is trying to access /api/users/alice, she would get much more of her info (such as private settings, etc) than user "bob" who would simply get her public profile.
I am currently using flask_restful with httpbasicauth. Right now I have the following:
class UserAPI(flask_restful.Resource):
#g.auth.login_required
def get(self, username):
# here is where I want to get the HTTPBasicAuth username
# to determine how much data to return
user = User.objects(username=username).exclude('password').first()
if user is not None:
return user.to_json()
else:
flask_restful.abort(404, message='User not found: ' + username)
The issue is that I cannot seem to figure out a CLEAN way to get the HTTP basic auth data. I know I could parse the request and decode the base-64 data, but I feel I shouldn't have to do that. Or, even better, find a way to pass the user_id from /api/users/[user_id] into the login_required annotation.
I feel this is would be a very common use case so I can't figure out why I can't find anything in this area. Am I designing this completely wrong?
Thanks very much!
I suggest not using flask.ext.httpauth. I didn't find it very useful. I use a decorator that takes the Authorization header and checks it with the db. You can access the username entered in request.authorization.username and the password is in request.authorization.password.
from flask import request
from flask.ext.restful import abort, Resource
from functools import wraps
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth:
abort(401)
user = User.objects(username=auth.username).first()
auth_ok = False
if user != None:
auth_ok = verify_password(auth.password) == user.password
if not auth_ok:
return abort(401)
return f(*args, **kwargs)
return decorated
class UserAPI(Resource):
#requires_auth
def get(self):
user = User.objects(username=request.authorization.username).\
exclude('password').first()
if user is not None:
return user.to_json()
else:
abort(404, message='User not found: ' + username)

Django and Middleware which uses request.user is always Anonymous

I'm trying to make middleware which alters some fields for the user based on subdomain, etc...
The only problem is the request.user always comes in as AnonymousUser within the middleware, but is then the correct user within the views. I've left the default authentication and session middleware django uses within the settings.
There is a similar question here: Django, request.user is always Anonymous User
But doesn't overly answer the total question because I'm not using different authentication methods, and djangos authentication is running before I invoke my own middleware.
Is there a way, while using DRF, to get the request.user within the middleware? I'll show some sample code here:
class SampleMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
#This will be AnonymousUser. I need it to be the actual user making the request.
print (request.user)
def process_response(self, request, response):
return response
with process_request:
class SampleMiddleware(object):
def process_request(self, request):
#This will be AnonymousUser. I need it to be the actual user making the request.
print (request.user)
def process_response(self, request, response):
return response
I've solved this problem by getting DRF token from the requests and loading request.user to the user associated to that model.
I had the default django authentication and session middleware, but it seems DRF was using it's token auth after middleware to resolve the user (All requests were CORS requests, this might have been why). Here's my updated middleware class:
from re import sub
from rest_framework.authtoken.models import Token
from core.models import OrganizationRole, Organization, User
class OrganizationMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
header_token = request.META.get('HTTP_AUTHORIZATION', None)
if header_token is not None:
try:
token = sub('Token ', '', header_token)
token_obj = Token.objects.get(key = token)
request.user = token_obj.user
except Token.DoesNotExist:
pass
#This is now the correct user
print (request.user)
This can be used on process_view or process_request as well.
Hopefully this can help someone out in the future.
Came across this today while having the same problem.
TL;DR;
Skip below for code example
Explanation
Thing is DRF have their own flow of things, right in the middle of the django request life-cycle.
So if the normal middleware flow is :
request_middleware (before starting to work on the request)
view_middleware (before calling the view)
template_middleware (before render)
response_middleware (before final response)
DRF code, overrides the default django view code, and executes their own code.
In the above link, you can see that they wrap the original request with their own methods, where one of those methods is DRF authentication.
So back to your question, this is the reason using request.user in a middleware is premature, as it only gets it's value after view_middleware** executes.
The solution I went with, is having my middleware set a LazyObject.
This helps, because my code (the actual DRF ApiVIew) executes when the actual user is already set by DRF's authentication.
This solution was proposed here together with a discussion.
Might have been better if DRF had a better way to extend their functionality, but as things are, this seems better than the provided solution (both performance and readability wise).
Code Example
from django.utils.functional import SimpleLazyObject
def get_actual_value(request):
if request.user is None:
return None
return request.user #here should have value, so any code using request.user will work
class MyCustomMiddleware(object):
def process_request(self, request):
request.custom_prop = SimpleLazyObject(lambda: get_actual_value(request))
The accepted answer only takes TokenAuthentication in consideration - in my case, there are more authentication methods configured. I thus went with initializing the DRF's Request directly which invokes DRF's authentication machinery and loops through all configured authentication methods.
Unfortunately, it still introduces an additional load on the database since the Token object must be queried (the accepted answer has this problem as well). The trick with SimpleLazyObject in this answer is a much better solution, but it didn't work for my use case because I need the user info in the middleware directly - I'm extending the metrics in django_prometheus and it processes the request before get_response is called.
from rest_framework.request import Request as RestFrameworkRequest
from rest_framework.views import APIView
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
drf_request: RestFrameworkRequest = APIView().initialize_request(request)
user = drf_request.user
...
return self.get_response(request)
Based on Daniel Dubovski's very elegant solution above, here's an example of middleware for Django 1.11:
from django.utils.functional import SimpleLazyObject
from organization.models import OrganizationMember
from django.core.exceptions import ObjectDoesNotExist
def get_active_member(request):
try:
active_member = OrganizationMember.objects.get(user=request.user)
except (ObjectDoesNotExist, TypeError):
active_member = None
return active_member
class OrganizationMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
request.active_member = SimpleLazyObject(lambda: get_active_member(request))
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
Daniel Dubovski's solution is probably the best in most cases.
The problem with the lazy object approach is if you need to rely on the side effects. In my case, I need something to happen for each request, no matter what.
If I'd use a special value like request.custom_prop, it has to be evaluated for each request for the side effects to happen. I noticed that other people are setting request.user, but it doesn't work for me since some middleware or authentication class overwrites this property.
What if DRF supported its own middleware? Where could I plug it in? The easiest way in my case (I don't need to access the request object, only the authenticated user) seems to be to hook into the authentication class itself:
from rest_framework.authentication import TokenAuthentication
class TokenAuthenticationWithSideffects(TokenAuthentication):
def authenticate(self, request):
user_auth_tuple = super().authenticate(request)
if user_auth_tuple is None:
return
(user, token) = user_auth_tuple
# Do stuff with the user here!
return (user, token)
Then I could just replace this line in my settings:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
#"rest_framework.authentication.TokenAuthentication",
"my_project.authentication.TokenAuthenticationWithSideffects",
),
# ...
}
I'm not promoting this solution, but maybe it will help someone else.
Pros:
It to solves this specific problem
There's no double authentication
Easy to maintain
Cons:
Not tested in production
Things happen in an unexpected place
Side effects...
I know it's not exactly answering the 'can we access that from the middleware' question, but I think it's a more elegant solution VS doing the same work in the middleware VS what DRJ does in its base view class. At least for what I needed, it made more sense to add here.
Basically, I'm just overriding the method 'perform_authentication()' from DRF's code, since I needed to add more things related to the current user in the request. The method just originally call 'request.user'.
class MyGenericViewset(viewsets.GenericViewSet):
def perform_authentication(self, request):
request.user
if request.user and request.user.is_authenticated():
request.my_param1 = 'whatever'
After that in your own views, instead of settings APIView from DRF as a parent class, simply set that class as a parent.
I wasn't quite happy with the solutions out there. Here's a solution that uses some DRF internals to make sure that the correct authentication is applied in the middleware, even if the view has specific permissions classes. It uses the middleware hook process_view which gives us access to the view we're about to hit:
class CustomTenantMiddleware():
def process_view(self, request, view_func, view_args, view_kwargs):
# DRF saves the class of the view function as the .cls property
view_class = view_func.cls
try:
# We need to instantiate the class
view = view_class()
# And give it an action_map. It's not relevant for us, but otherwise it errors.
view.action_map = {}
# Here's our fully formed and authenticated (or not, depending on credentials) request
request = view.initialize_request(request)
except (AttributeError, TypeError):
# Can't initialize the request from this view. Fallback to using default permission classes
request = APIView().initialize_request(request)
# Here the request is fully formed, with the correct permissions depending on the view.
Note that this doesn't avoid having to authenticate twice. DRF will still happily authenticate right afterwards.
I had the same issue and decided to change my design. Instead of using a Middleware I simply monkey-patch rest_framework.views.APIView.
In my case I needed to patch check_permissions but you can patch whatever fits your problem. Have a look at the the source code.
settings.py
INSTALLED_APPS = [
..
'myapp',
]
myapp/patching.py
import sys
from rest_framework.views import APIView as OriginalAPIView
class PatchedAPIView(OriginalAPIView):
def check_permissions(self, request):
print(f"We should do something with user {request.user}"
return OriginalAPIView.check_permissions(self, request)
# We replace the Django REST view with our patched one
sys.modules['rest_framework'].views.APIView = PatchedAPIView
myapp/__init__.py
from .patching import *

login() in Django testing framework

I have started using Django's testing framework, and everything was working fine until I started testing authenticated pages.
For the sake of simplicity, let's say that this is a test:
class SimpleTest(TestCase):
def setUp(self):
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
c = Client()
print c.login(username='temporary', password='temporary')
response = c.get('/users/secure/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
After I run this test, it fails, and I see that printing return value of login() returns True, but response.content gets redirected to login page (if login fails authentication decorator redirects to login page). I have put a break point in decorator that does authentication:
def authenticate(user):
if user.is_authenticated():
return True
return False
and it really returns False. Line 4 in test_secure_page() properly retrieves user.
This is the view function:
#user_passes_test(authenticate, login_url='/users/login')
def secure(request):
user = request.user
return render_to_response('secure.html', {'email': user.email})
Of course, if I try to login through application (outside of test), everything works fine.
The problem is that you're not passing RequestContext to your template.
Also, you probably should use the login_required decorator and the client built in the TestCase class.
I'd rewrite it like this:
#views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model
#login_required(login_url='/users/login')
def secure(request):
user = request.user
return render(request, 'secure.html', {'email': user.email})
#tests.py
class SimpleTest(TestCase):
def setUp(self):
User = get_user_model()
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
User = get_user_model()
self.client.login(username='temporary', password='temporary')
response = self.client.get('/manufacturers/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
It can often be useful to use a custom auth backend that bypassess any sort of authentication during testing:
from django.contrib.auth import get_user_model
class TestcaseUserBackend(object):
def authenticate(self, testcase_user=None):
return testcase_user
def get_user(self, user_id):
User = get_user_model()
return User.objects.get(pk=user_id)
Then, during tests, add yourapp.auth_backends.TestcaseUserBackend to your AUTHENTICATION_BACKENDS:
AUTHENTICATION_BACKENDS = [
"akindi.testing.auth_backends.TestcaseUserBackend",
]
Then, during tests, you can simply call:
from django.contrib.auth import login
user = User.objects.get(…)
login(testcase_user=user)
Token based authentication:
I was in same situation. I found solution in which actually I did generate a user for login purpose in setUp method. Then later in the test methods, I tried to get the token and passed it along with request data.
setUp:
create a user
self.pravesh = User.objects.create(
email='psj.aaabbb#gmail.com',
first_name='Pravesh',
last_name='aaabbb',
phone='5456165156',
phonecountrycode='91'
)
set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)
test_method:
create client
client.login(email=self.pravesh.email, password=self.password)
get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
pass login information
response = client.post(
reverse('account:post-data'),
data = json.dumps(self.data),
HTTP_AUTHORIZATION='Token {}'.format(token),
content_type = 'application/json'
)

Is it possible to implement a "change password at next logon" type feature in the django admin?

I want to be able to set an option in the user's settings that forces them to change their password upon the next login to the admin interface. Is this possible? How would it go about being implemented? I'm using the default auth model right now but not opposed to modifying or changing it. Thanks for any help.
I'm actually in the process of doing this myself. You need three components: a user profile (if not already in use on your site), a middleware component, and a pre_save signal.
My code for this is in an app named 'accounts'.
# myproject/accounts/models.py
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
force_password_change = models.BooleanField(default=False)
def create_user_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
def password_change_signal(sender, instance, **kwargs):
try:
user = User.objects.get(username=instance.username)
if not user.password == instance.password:
profile = user.get_profile()
profile.force_password_change = False
profile.save()
except User.DoesNotExist:
pass
signals.pre_save.connect(password_change_signal, sender=User, dispatch_uid='accounts.models')
signals.post_save.connect(create_user_profile_signal, sender=User, dispatch_uid='accounts.models')
First, we create a UserProfile with a foreign key to User. The force_password_change boolean will, as its name describes, be set to true for a user whenever you want to force them to change their password. You could do anything here though. In my organization, we also chose to implement a mandatory change every 90 days, so I also have a DateTimeField that stores the last time a user changed their password. You then set that in the pre_save signal, password_changed_signal.
Second, we have the create_user_profile_signal. This is mostly added just for completeness. If you're just now adding user profiles into your project, you'll need a post_save signal that will create a UserProfile every time a User is created. This accomplishes that task.
Third, we have the password_changed_signal. This is a pre_save signal because at this point in the process the actual row in the User table hasn't be updated. Therefore, we can access both the previous password and the new password about to be saved. If the two don't match, that means the user has changed their password, and we can then reset the force_password_change boolean. This would be the point, also where you would take care of any other things you've added such as setting the DateTimeField previously mentioned.
The last two lines attach the two functions to their appropriate signals.
If you haven't already, you will also need to add the following line to your project's settings.py (changing the app label and model name to match your setup):
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
That covers the basics. Now we need a middleware component to check the status of our force_password_change flag (and any other necessary checks).
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
import re
class PasswordChangeMiddleware:
def process_request(self, request):
if request.user.is_authenticated() and \
re.match(r'^/admin/?', request.path) and \
not re.match(r'^/admin/password_change/?', request.path):
profile = request.user.get_profile()
if profile.force_password_change:
return HttpResponseRedirect('/admin/password_change/')
This very simple middleware hooks into the process_request stage of the page loading process. It checks that 1) the user has already logged in, 2) they are trying to access some page in the admin, and 3) the page they are accessing is not the password change page itself (otherwise, you'd get an infinite loop of redirects). If all of these are true and the force_password_change flag has been set to True, then the user is redirected to the password change page. They will not be able to navigate anywhere else until they change their password (firing the pre_save signal discussed previously).
Finally, you just need to add this middleware to your project's settings.py (again, changing the import path as necessary):
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I have used Chris Pratt's solution, with a little change: instead of using a middleware, that'd be executed for every page with the consequent resource use, I figured I'd just intercept the login view.
In my urls.py I have added this to my urlpatterns:
url(r'^accounts/login/$', 'userbase.views.force_pwd_login'),
then I added the following to userbase.views:
def force_pwd_login(request, *args, **kwargs):
response = auth_views.login(request, *args, **kwargs)
if response.status_code == 302:
#We have a user
try:
if request.user.get_profile().force_password_change:
return redirect('django.contrib.auth.views.password_change')
except AttributeError: #No profile?
pass
return response
It seems to work flawlessly on Django 1.2, but I have no reason to believe 1.3+ should have problems with it.
This is the middleware I use with Django 1.11 :
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
from django.urls import reverse
class PasswordChangeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
next = reverse('client:password-update')
if request.user.is_authenticated() and request.path != next:
if request.user.account.force_password_change:
return HttpResponseRedirect(next)
return response
Still adding it to the settings middleware list :
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I spent 2 days on this issue recently, and a new solution came out.
Hopefully it's useful.
Just as above said, a new user model created.
newuser/models.py
class Users(AbstractUser):
default_pwd_updated = models.NullBooleanField(default=None, editable=False)
pwd_update_time = models.DateTimeField(editable=False, null=True, default=None) # reserved column to support further interval password (such as 60 days) update policy
def set_password(self, raw_password):
if self.default_pwd_updated is None:
self.default_pwd_updated = False
elif not self.default_pwd_updated:
self.default_pwd_updated = True
self.pwd_update_time = timezone.now()
else:
self.pwd_update_time = timezone.now()
super().set_password(raw_password)
Set this model as the AUTH_USER_MODEL.
[project]/settings.py
AUTH_USER_MODEL = 'newuser.Users'
Now you just need to customize LoginView and some methods in AdminSite.
[project]/admin.py
from django.contrib.admin import AdminSite
from django.contrib.auth.views import LoginView
from django.utils.translation import gettext as _, gettext_lazy
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
class NewLoginView(LoginView):
def get_redirect_url(self):
if self.request.method == "POST" and self.request.user.get_username()\
and not self.request.user.default_pwd_updated:
redirect_to = reverse("admin:password_change")
else:
redirect_to = self.request.POST.get(
self.redirect_field_name,
self.request.GET.get(self.redirect_field_name, '')
)
return redirect_to
class NewAdminSite(AdminSite):
site_header = site_title = gettext_lazy("Customized Admin Site")
def __init__(self, name="admin"):
super().__init__(name)
#never_cache
def login(self, request, extra_context=None):
"""
Display the login form for the given HttpRequest.
"""
if request.method == 'GET' and self.has_permission(request):
# Already logged-in, redirect to admin index
if request.user.get_username() and not request.user.default_pwd_updated:
# default password not changed, force to password_change view
path = reverse('admin:password_change', current_app=self.name)
else:
path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(path)
from django.contrib.auth.views import LoginView
from django.contrib.admin.forms import AdminAuthenticationForm
context = {
**self.each_context(request),
'title': _('Log in'),
'app_path': request.get_full_path(),
'username': request.user.get_username(),
}
if (REDIRECT_FIELD_NAME not in request.GET and
REDIRECT_FIELD_NAME not in request.POST):
context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
context.update(extra_context or {})
defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin/login.html',
}
request.current_app = self.name
return NewLoginView.as_view(**defaults)(request) # use NewLoginView
#never_cache
def index(self, request, extra_context=None):
if request.user.get_username() and not request.user.default_pwd_updated:
# if default password not updated, force to password_change page
context = self.each_context(request)
context.update(extra_context or {})
return self.password_change(request, context)
return super().index(request, extra_context)
admin_site = NewAdminSite(name="admin")
NOTE: if you intend to use custom template for changing default password, you could override each_context method and then determine which template should be used up to the flag force_pwd_change.
[project]/admin.py
def using_default_password(self, request):
if self.has_permission(request) and request.user.get_username() and not request.user.default_pwd_updated:
return True
return False
def each_context(self, request):
context = super().each_context(request)
context["force_pwd_change"] = self.using_default_password(request)
return context
From a thread on the Django Users mailing list:
This isn't ideal, but it should work
(or prompt someone to propose
something better).
Add a one-to-one table for the user,
with a field containing the initial
password (encrypted, of course, so it
looks like the password in the
auth_user table).
When the user logs in, have the login
page check to see if the passwords
match. If they do, redirect to the
password change page instead of the
normal redirect page.
Checkout this simple package based on session (Tested with django 1.8). https://github.com/abdullatheef/django_force_reset_password
Create custom view in myapp.views.py
class PassWordReset(admin.AdminSite):
def login(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).login(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
if not "fpr" in request.session or request.session['fpr']:
request.session['fpr'] = True
return HttpResponseRedirect("/admin/password_change/")
return response
return super(PassWordReset, self).login(request, extra_context=extra_context)
def password_change(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).password_change(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
request.session['fpr'] = False
return response
return super(PassWordReset, self).password_change(request, extra_context=extra_context)
pfr_login = PassWordReset().login
pfr_password_change = PassWordReset().admin_view(PassWordReset().password_change, cacheable=True)
Then in project/urls.py
from myapp.views import pfr_password_change, pfr_login
urlpatterns = [
......
url(r'^admin/login/$', pfr_login),
url(r'^admin/password_change/$', pfr_password_change),
url(r'^admin/', admin.site.urls),
....
]
Then add this middleware myapp/middleware.py
class FPRCheck(object):
def process_request(self, request):
if request.user.is_authenticated() \
and re.match(r'^/admin/?', request.path) \
and (not "fpr" in request.session or ("fpr" in request.session and request.session['fpr'])) \
and not re.match(r"/admin/password_change|/admin/logout", request.path):
return HttpResponseRedirect("/admin/password_change/")
Order of middleware
MIDDLEWARE_CLASSES = [
....
'myapp.middleware.FPRCheck'
]
Note
This will not need any extra model.
Also work with any Session Engine.
No db query inside middleware.

Categories