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
Related
Project's backend and frontend are separate and trying to implement provider login via google.
in settings.py
LOGIN_REDIRECT_URL = "http://localhost:3000"
SOCIALACCOUNT_ADAPTER = "users.adapter.CustomOAuth2Adapter"
in adapter.py
class CustomOAuth2Adapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form):
user = sociallogin.user
user.is_active = True
user.save()
token = Token.objects.create(user=user)
response = HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
response.set_cookie('auth_token', token)
return response
def pre_social_login(self, request, sociallogin):
try:
user = User.objects.get(email=sociallogin.user.email)
user.is_active = True
user.save()
request.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
# sociallogin.connect(request, user)
# return response
except:
pass
there is one main problem here. when login or save has success trying to redirect from backend to frontend. It's using LOGIN_REDIRECT_URL and works. but when it's redirecting I try to set token into cookie. but it doesn't set.
request.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
Additionally I tried to set cookie like below.
def pre_social_login(self, request, sociallogin):
try:
user = User.objects.get(email=sociallogin.user.email)
user.is_active = True
user.save()
# sociallogin.connect(request, user)
response = HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
response.set_cookie('auth_token', request.user.auth_token.key, domain=settings.LOGIN_REDIRECT_URL)
return response
except:
pass
but this doesn't work either. when backend and frontend are separate how to send token in redirection response.
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.
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)
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
So I'm using Rdio to login and create users, and wrote a backend to handle its oauth. The first time you try to sign in using Rdio, it creates a user and an attached Rdio user, but it doesn't create a session and return the session cookie.
The flow is like any oauth2 flow: you press a button on my app, it redirects w/ get params to Rdio, and Rdio calls a callback view on my app (along with a code in the GET params). In that callback view, I call authenticate:
class RdioCallbackView(View):
def get(self, request):
""" here, you need to create and auth a django user and create and tie the rdio user's stuff to it """
if request.user.is_authenticated() == False:
try:
rdio_code = request.GET['code']
except KeyError:
return redirect(reverse('login'))
# authenticate
user = auth.authenticate(rdio_code=rdio_code)
if user is not None and user.is_active:
auth.login(request, user)
else:
return render(request, 'home/login.html', {'rdio_url': create_rdio_auth_url(), 'message': "That code didn't seem to work"})
else:
# user exists!
user = request.user
return HttpResponseRedirect(reverse('the-next-view'))
The custom auth backend looks like this:
class RdioBackend(object):
def authenticate(self, rdio_code=None):
token_info = exchange_rdio_code(rdio_code)
try:
access_token = token_info['access_token']
refresh_token = token_info['refresh_token']
except KeyError:
return None
except TypeError:
# the code was probably already used.
return None
rdio_user_dict = get_rdio_user_for_access_token(access_token)
rdio_key = rdio_user_dict['key']
try:
rdio_user = RdioUser.objects.get(rdio_id=rdio_key)
rdio_user.access_token = access_token
rdio_user.refresh_token = refresh_token
rdio_user.save()
user = rdio_user.user
except RdioUser.DoesNotExist:
user = User.objects.create(username=rdio_key)
user.set_unusable_password()
rdio_user = RdioUser.objects.create(
rdio_id = rdio_key,
access_token = access_token,
refresh_token = token_info['refresh_token'],
user = user,
)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And that's where things get weird. It doesn't seem to make a new Session object, and definitely doesn't return a session cookie. However, when I go back and do the Rdio login again for a second time, it returns a session cookie, makes the session on the backend, and login and auth work perfectly.
And I think my AUTHENTICATION_BACKENDS settings is right:
AUTHENTICATION_BACKENDS = (
'appname.backend.RdioBackend',
'django.contrib.auth.backends.ModelBackend',
)
Edit: More possibly relevant info:
The views that it's redirecting to have a LoginRequiredMixin:
class LoginRequiredMixin(object):
#classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
And in RdioCallbackView, when I change the final line from return HttpResponseRedirect(reverse('the-next-view')) to instead just serve the template directly with return render(request, 'path/to.html', param_dict), it does serve the cookie and make a sessionid, but then it deletes it from the DB and from the browser the moment I navigate away from that screen.
This might be the dumbest bug ever. It turns out that if you create a user without a password, you don't need to call user.set_unusable_password(). And if you do call user.set_unusable_password(), it somehow messes with any auth you do (even AFTER you call that).
So to fix this, I just got rid of the call to user.set_unusable_password() in my custom django auth backend.