How does Django log in multiple users in a browser and how to avoid session coverage? - python-django-storages

This is mine views.py file
class UserAPIView(TemplateView, ListCreateAPIView):
serializer_class = UserSerializer
queryset = UserProfile.objects.all()
template_name = 'users/login.html'
def post(self, request, *args, **kwargs):
if self.find_password_and_user(request):
return HttpResponseRedirect(reverse('user:home'))
else:
return HttpResponse("False")
def find_password_and_user(self, request):
print(request)
post_username = request.data.get('username')
post_password = request.data.get('password')
user = authenticate(username=post_username, password=post_password)
if user is not None:
# login(request, user)
# =====
backend = None
session_auth_hash = ''
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
# =====
user = UserProfile.objects.filter(username=post_username)
u_password = user.values('password')[0].get('password')
return check_password(post_password, u_password)
return False
How can I log in multiple users in one browser at the same time
By default, the session of the newly logged in user will override the session of the previous user. (in Django_ In the session table, the session of the previously logged in user is overwritten.)
If it is a different browser, it will not be covered. In short, the same browser can only log in to one user at the same time.
On the Internet, there is a way to change the session into a list, but there is no clue at all. I can't help it. Thank you very much.

The answer above got two steps. I should have said something wrong. But I haven't found a solution to this problem for nearly a day. This is my first time to use stack overflow, which is also a memorial. ha-ha. Thank you

Related

Django: invalid token for password reset after account creation

within an application, a user with an administrator role, through a DRF endpoint, is able to create new user accounts.
The need is to automatically send the password reset link to the emails of the newly created users.
I have defined an url:
path('v1/account/register/',
AccountCreationView.as_view(),
name='custom_account_creation'),
the view that first of all check that user role allow the creations of new users:
class AccountCreationView(RegisterView):
"""
Accounts Creation
"""
serializer_class = RegisterWithMailSendSerializer
def get_response_data(self, user):
# print('get_response_data', user)
self.user = user
def create(self, request, *args, **kwargs):
role_section = 'UsersAdmins'
#
rights_check = role_rights_check(
request.user,
role_section,
"R",
)
if rights_check[0] == False:
return Response({"error": rights_check[1]},
status=status.HTTP_401_UNAUTHORIZED)
response = super().create(request, *args, **kwargs)
and a custom serializer for that views, where after validating data, save and then create the password reset link and send via email to the newly created user:
class RegisterWithMailSendSerializer(RegisterSerializer):
def save(self, request, **kwargs):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user = adapter.save_user(request, user, self, commit=False)
if "password1" in self.cleaned_data:
try:
adapter.clean_password(self.cleaned_data['password1'],
user=user)
except DjangoValidationError as exc:
raise serializers.ValidationError(
detail=serializers.as_serializer_error(exc))
user.save()
self.custom_signup(request, user)
setup_user_email(request, user, [])
pg = PasswordResetTokenGenerator()
pg_token = pg.make_token(user)
print('>>> pg_token', pg_token)
frontend_site = settings.FRONTEND_APP_BASE_URL
token_generator = kwargs.get('token_generator',
default_token_generator)
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
email = self.get_cleaned_data()['email']
get_adapter(request).send_mail('password_reset_key', email, context)
return user
in settings.py
CSRF_COOKIE_SECURE isn't set and has it's default False value.
everything seems to work, the user is created and the link with uid and token is sent to the relative email BUT the token seem is invalid when the user tries to reset his password...
Printed 'pg_token' is the same founded into the sended URL.
For completeness here the custom serializer used to reset the password:
in settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'api.serializers.serializers_auth.CustomPasswordResetSerializer',
'TOKEN_SERIALIZER': 'api.serializers.serializers_auth.TokenSerializer',
}
serializers_auth.py
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def save(self, request, **kwargs):
frontend_site = settings.FRONTEND_APP_BASE_URL
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator',
default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail('password_reset_key', email,
context)
return self.cleaned_data['email']
class CustomPasswordResetSerializer(PasswordResetSerializer):
#property
def password_reset_form_class(self):
return CustomAllAuthPasswordResetForm
I tried everything, including the same calls for creation and reset through Postman thinking that, for some reason, the token was invalidated by the automatic login in the DRF web interface after the user was created but I don't understand why the token is not valid.
If i try manually POST email address on /api/v1/auth/password/reset/ and then use provided uid/token on /api/v1/auth/password/reset/confirm/ the password reset works as expected.
Some experience and tips are really appreciated.
you could easily implement full user authentication with Django Djoser
Check the docs: https://djoser.readthedocs.io/en/latest/getting_started.html
Available endpoints
/users/
/users/me/
/users/confirm/
/users/resend_activation/
/users/set_password/
/users/reset_password/
/users/reset_password_confirm/
/users/set_username/
/users/reset_username/
/users/reset_username_confirm/
/token/login/ (Token Based Authentication)
/token/logout/ (Token Based Authentication)
/jwt/create/ (JSON Web Token Authentication)
/jwt/refresh/ (JSON Web Token Authentication)
/jwt/verify/ (JSON Web Token Authentication)
Solved by calling password reset endpoint with email parameter immediately after the user is created, without any custom logic or overrides:
from rest_framework.test import APIClient
if settings.SEND_EMAIL_PWD_CHANGE_TO_NEW_USERS == True:
client = APIClient()
client.post('/api/v1/auth/password/reset/', {'email': user.email}, format='json')
And now the email with the reset link contain a valid token for the password reset.

Customizing authentication backend for multi users in Django

So my question is that you have a custom user 'Account' and I'm trying to use Loginview but with only one specific backend but the class does not care about the value passed.
from django.contrib.auth import views as auth_views
from .lists import ProjectCreateView , ProjectUpdateView , ProjectDeleteView
class ClientLogin(auth_views.Loginview):
def form_valid(self, form):
"""Security check complete. Log the user in."""
auth_login(self.request, form.get_user(),'CREA.models.ClientBackend')
return HttpResponseRedirect(self.get_success_url())
urlpatterns = [
path('', ClientLogin.as_view(template_name='authentification/client_login.html',
redirect_authenticated_user=True,next_page='client-home'), name='client-log-in'),
NB: note that the backend works fine if it's the only one specified in the settings but other than that the loginview use all backends one by one, and yes I know that this is the default behaviour but then whats the point of this variable in auth_login
here is the buld in function in django :
def login(request, user, backend=None):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ""
if user is None:
user = request.user
if hasattr(user, "get_session_auth_hash"):
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash
and not constant_time_compare(
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
)
):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument or set the "
"`backend` attribute on the user."
)
else:
if not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, "user"):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)

django 1.6 Why not just 'user.last_login = timezone.now()' and 'user.save(update_fields=['last_login'])'?

The django C:\Python33\Lib\site-packages\django\contrib\auth\__init__.py ,fucntion login is:
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.pk:
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.pk
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
As you can see, the last line is to update the user field 'last_login' .It uses the signal mechanism and it is really very complicated. There is a large block of code behind this line, it is so large that I don't want to paste it here. If you are interested, you can click here to see it.
Why not just use the two lines below instead of that magic line?
def login(request, user):
#....
rotate_token(request)
user.last_login = timezone.now()
user.save(update_fields=['last_login'])
Using the signal, you can hook into login process, without modifying django.contrib.auth code.
This ticket from django trac contains discussion about login/logout signal.
Related commit: https://github.com/django/django/commit/132afbf8eee837b6fe2d051f7eced4889e19de88

django-webtest, user authentication and view decorators

I'm using django-webtest (v1.5.6) to test a decorator is limiting access to a view to authenticated users.
My view is simply:
#active_account_required
def homepage(request):
return render(request, 'accounts/account_homepage.html', {
'user': request.user,
})
The active_account_required decorator is:
def active_account_required(function = None):
"""
Check the user is both logged in and has a valid, activated user account.
If a user tries to access a view protected by this decorator, they will be
redirected accordingly.
See http://passingcuriosity.com/2009/writing-view-decorators-for-django/
"""
def _dec(view_func):
def _view(request, *args, **kwargs):
if request.user.is_anonymous():
return HttpResponseRedirect(reverse_lazy('auth_login'))
if not request.user.get_profile().is_activated():
return HttpResponseRedirect(reverse_lazy('registration_activation_incomplete'))
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
My test method is
class AccountViewsTests(WebTest):
def test_activated_user_can_access_account_homepage(self):
"""
Test an activated user can access the account homepage
"""
user = G(User)
user.get_profile().datetime_activated = timezone.now()
res = self.app.get(reverse('account_homepage'), user = user)
pdb.set_trace()
self.assertTemplateUsed(res, 'accounts/account_homepage.html',
'Activated account did not access account homepage correctly')
(The user object is created using the G function from django-dynamic-fixture)
When running the test, the decorator is preventing access to the homepage view.
You can see I'm using pdb to inspect the objects. User is a valid user object that should pass all the tests in the active_account_required decorator:
(Pdb) user
<User: 2>
(Pdb) user.is_anonymous()
False
(Pdb) user.get_profile().is_activated()
True
Despite the user being correct, the response from self.app.get(reverse('account_homepage'), user = user) is a 302 redirect to the registration_activation_incomplete URL as per the decorator code:
(Pdb) res
<302 FOUND text/html location: http://localhost:80/accounts/registration/activate/incomplete/ no body>
It appears the user object is not being sent correctly in the WebTest request, but this matches the django-webtest documentation. I've also tried passing the user in by username as user='2' but get the same result.
Any ideas?
Oops - the problem is that I simply forgot to save my user profile after setting the activation timestamp!
Changing the test code to:
user = G(User)
user.get_profile().datetime_activated = timezone.now()
user.get_profile().save()
res = self.app.get(reverse('account_homepage'), user = user)
i.e. adding user.get_profile().save() got it working :)
Sorry for the noise.

Django - doing work as another user

I'm using celery and would like to drive sessions on behalf of the user who submitted the request rather than the "root" user. For example a basic task looks like this (a very contrived example)
#task
def process_checklist(**kwargs):
log = process_checklist.get_logger()
document = kwargs.get('document', None)
company = kwargs.get('company', None)
user = kwargs.get('user', None)
object = Book.object.get_or_create(name=kwargs.get('name'))
There are tradeoffs to doing this but I feel it would be far more beneficial to actually use the views to do this, very similar to how we test things.. Practially speaking this is used for batch uploading of data, where each row is effectively a CreateView.
client = Client()
client.login(user='foo', password='bar')
client.post(reverse('create_book_view', data=**kwargs))
But I can't think of a good way to practically use (if it's possible) the django.test.client Client class to log a user in without knowing the password and fill in a view for them. I thought of this but I'm sure there is a better way??
Here is what I came up with?
class AxisClient(Client):
# The admin account is created by us. Once that is done everything should be tested through
# the system.
def login_user(self, username):
"""
Sets the Factory to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
user = User.objects.get(username=username)
user.backend = None
if user and user.is_active \
and 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = import_module(settings.SESSION_ENGINE)
# Create a fake request to store login details.
request = HttpRequest()
if self.session:
request.session = self.session
else:
request.session = engine.SessionStore()
login(request, user)
# Save the session values.
request.session.save()
# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.cookies[session_cookie].update(cookie_data)
return True
else:
return False
This appears to work!
class AxisClient(Client):
# The admin account is created by us. Once that is done everything should be tested through
# the system.
def login_user(self, username):
"""
Sets the Factory to appear as if it has successfully logged into a site.
Returns True if login is possible; False if the provided credentials
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
user = User.objects.get(username=username)
user.backend = None
if user and user.is_active \
and 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = import_module(settings.SESSION_ENGINE)
# Create a fake request to store login details.
request = HttpRequest()
if self.session:
request.session = self.session
else:
request.session = engine.SessionStore()
login(request, user)
# Save the session values.
request.session.save()
# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.cookies[session_cookie].update(cookie_data)
return True
else:
return False