Django redirect from resource not working - django

I have this in my resource:
def after_import_instance(self, instance, new, row_number=None, **kwargs):
...
base_url = 'https://account.box.com/api/oauth2/authorize'
client_id = '[CLIENT_ID]'
auth_url = f'{base_url}?client_id={client_id}&response_type=code'
print(auth_url)
return redirect(auth_url)
the auth_url is being printed, but the redirect is not working. Here is the print:
[2022-09-01 05:07:10,617: WARNING/MainProcess] https://account.box.com/api/oauth2/authorize?client_id=[CLIENT_ID]&response_type=code

Related

Django allauth overriding DefaultSocialAdapter

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.

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.

DRF simplejwt refresh access_token stored in HTTPonly cookies

I'm doing a project using React and django, I have used a DRF SimpleJWT for authentication. I have stored a access and refresh token in HTTPOnly cookies all are working fine but I didn't find the way to refresh the token. I can't make it through by reading a documentation. If somebody has done it before please share the code
Hope I'm not late.
An easy way, you can use Dj-Rest-Auth to handle everything.
Otherwise,
If you want to use function views you can use in your views.py and add its URL to urls.py:
#api_view(['POST'])
#permission_classes([AllowAny])
#csrf_protect
def refresh_token_view(request):
User = get_user_model()
refresh_token = request.COOKIES.get('refreshtoken')
if refresh_token is None:
raise exceptions.AuthenticationFailed(
'Authentication credentials were not provided.')
try:
payload = jwt.decode(
refresh_token, settings.REFRESH_TOKEN_SECRET, algorithms['HS256'])
except jwt.ExpiredSignatureError:
raise exceptions.AuthenticationFailed(
'expired refresh token, please login again.')
user = User.objects.filter(id=payload.get('user_id')).first()
if user is None:
raise exceptions.AuthenticationFailed('User not found')
if not user.is_active:
raise exceptions.AuthenticationFailed('user is inactive')
access_token = generate_access_token(user)
return Response({'access_token': access_token})
If you want to use class views add this to your views.py:
from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenRefreshSerializer
from rest_framework_simplejwt.exceptions import InvalidToken
class CookieTokenRefreshSerializer(TokenRefreshSerializer):
refresh = None
def validate(self, attrs):
attrs['refresh'] =
self.context['request'].COOKIES.get('refresh_token')
if attrs['refresh']:
return super().validate(attrs)
else:
raise InvalidToken('No valid token found in cookie\'refresh_token\'')
class CookieTokenObtainPairView(TokenObtainPairView):
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get('refresh'):
cookie_max_age = 3600 * 24 * 14 # 14 days
response.set_cookie('refresh_token',response.data['refresh'],max_age=cookie_max_age, httponly=True )
del response.data['refresh']
return super().finalize_response(request, response, *args, **kwargs)
class CookieTokenRefreshView(TokenRefreshView):
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get('refresh'):
cookie_max_age = 3600 * 24 * 14 # 14 days
response.set_cookie('refresh_token',response.data['refresh'], max_age=cookie_max_age, httponly=True )
del response.data['refresh']
return super().finalize_response(request, response, *args, **kwargs)
serializer_class = CookieTokenRefreshSerializer
Add the below in url.py to use the above views to get and refresh:
from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views
urlpatterns = [
path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'),
]
source

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

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

Django test client http basic auth for post request

everyone. I am trying to write tests for RESTful API implemented using django-tastypie with http basic auth. So, I have the following code:
def http_auth(username, password):
credentials = base64.encodestring('%s:%s' % (username, password)).strip()
auth_string = 'Basic %s' % credentials
return auth_string
class FileApiTest(TestCase):
fixtures = ['test/fixtures/test_users.json']
def setUp(self):
self.extra = {
'HTTP_AUTHORIZATION': http_auth('testuser', 'qwerty')
}
def test_folder_resource(self):
response = self.client.get('/api/1.0/folder/', **self.extra)
self.assertEqual(response.status_code, 200)
def test_folder_resource_post(self):
response = self.client.post('/api/1.0/folder/', **self.extra)
self.assertNotEqual(response.status_code, 401)
GET request is done well, returning status code 200. But POST request always returns 401. I am sure I am doing something wrong. Any advice?
Check out this question. I've used that code for tests using both GET and POST and it worked. The only difference I can see is that you have used base64.encodestring instead of base64.b64encode.
Otherwise, if that doesn't work, how are you performing the HTTP Authentication? I wrote and use this function decorator:
import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate, login
def http_auth(view, request, realm="", must_be='', *args, **kwargs):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
if must_be in ('', uname):
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
login(request, user)
request.user = user
return view(request, *args, **kwargs)
# They mustn't be logged in
response = HttpResponse('Failed')
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
def http_auth_required(realm="", must_be=''):
""" Decorator that requires HTTP Basic authentication, eg API views. """
def view_decorator(func):
def wrapper(request, *args, **kwargs):
return http_auth(func, request, realm, must_be, *args, **kwargs)
return wrapper
return view_decorator
I've found a reason of my problem. DjangoAuthorization checks permissions with django premissions framework, since I don't use it in my project — all post/put/delete requests from non superuser are unauthorized. My bad.
Anyway, thanks a lot to you, guys, for responses.
On Python 3
#staticmethod
def http_auth(username, password):
"""
Encode Basic Auth username:password.
:param username:
:param password:
:return String:
"""
data = f"{username}:{password}"
credentials = base64.b64encode(data.encode("utf-8")).strip()
auth_string = f'Basic {credentials.decode("utf-8")}'
return auth_string
def post_json(self, url_name: AnyStr, url_kwargs: Dict, data: Dict):
"""
Offers a shortcut alternative to doing this manually each time
"""
header = {'HTTP_AUTHORIZATION': self.http_auth('username', 'password')}
return self.post(
reverse(url_name, kwargs=url_kwargs),
json.dumps(data),
content_type="application/json",
**header
)