I created this custom jwt_payload_handler for jwt so that it can be password aware. If a user changes their password, I want previous tokens to expire. But even though I have a custom handler, for some reason drf jwt ignores the changes and still accepts past tokens after I change the user password or change the password_last_change string value.
Am I missing another configuration?
jwt_payload_handler:
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
password_last_change = user.password_last_change # getting json not serializable error. constant string for now
warnings.warn(
'The following fields will be removed in the future: '
'`email` and `user_id`. ',
DeprecationWarning
)
payload = {
'user_id': user.pk,
'username': username,
'password': user.password,
'exp': (datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA) ,
'password_last_change': 'test1',
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
if api_settings.JWT_AUDIENCE is not None:
payload['aud'] = api_settings.JWT_AUDIENCE
if api_settings.JWT_ISSUER is not None:
payload['iss'] = api_settings.JWT_ISSUER
return payload
I figured it out. Instead of a custom jwt_payload_handler function we need to set JWT_GET_USER_SECRET_KEY value in the settings.py file. By default it is None but it accepts a function that takes a user as a parameter and returns a uuid string. If not configured, the conf django secret key is used for all users. But if set, each user will have their own secret key.
On password change, also update the uuid.
in the user model we could set some field to:
class TestUser(AbstractBaseUser):
jwt_secret = models.UUIDField(default=uuid.uuid4)
function example:
def jwt_get_secret_key(user_model):
return user_model.jwt_secret
in settings:
JWT_AUTH = {
...
'JWT_GET_USER_SECRET_KEY': 'path.to.jwt_get_secret_key'
...
}
So in your view or signal, after set_password method is called:
you can set the user.secret_jwt to a new value:
user.secret_jwt = uuid.uuid4()
user.save()
Which will make all previous request invalid.
Related
Im trying to create some tests for my django project. I get multiple errors when trying to do the views tests. Most of my views depend on a user to be logged in and I cant find the way to log in . Im using the deafult django built-in AUTH system.
VIEW :
#login_required
def fields(request):
if request.user.profile.user_package == "Livestock":
raise PermissionDenied()
field_list = Field.objects.filter(user = request.user)
context = {
"title": "Fields",
"field_list" : field_list,
}
template = 'agriculture/fields.html'
return render(request, template, context)
TetCase:
class TestViews(TestCase):
#classmethod
#factory.django.mute_signals(signals.pre_save, signals.post_save, signals.pre_delete, signals.post_delete)
def setUpTestData(cls):
# Create new user
test_user = User.objects.create(username='test_user',password='1XISRUkwtuK')
test_user.save()
c = Client()
profile = Profile.objects.get_or_create(user = test_user, user_package = 'hybrid')
c.login(username = test_user.username, password = test_user.password)
Field.objects.create(user=test_user,friendly_name='Arnissa')
def test_logged_in_user(self):
login = self.client.login(username='test_user', password='1XISRUkwtuK')
response = self.client.get(reverse('agriculture:fields'))
# Check our user is logged in
self.assertEqual(str(response.context['user']), 'test_user')
# Check that we got a response "success"
self.assertEqual(response.status_code, 200)
path: path('fields', views.fields, name='fields')
and settings if they provide any help :
LOGIN_REDIRECT_URL = 'dashboard:index'
LOGOUT_REDIRECT_URL = 'login'
LOGIN_URL = 'login'
On my tests i get the error TypeError: 'NoneType' object is not subscriptable when im checking if user is logged in. If i try to get the response I get AssertionError: 302 != 200.
When creating User in Django you should not use create method of user manager, because that sets the password to plain text instead of enrypting it. Try creating user using create_user method, like that:
test_user = User.objects.create_user(username='test_user',password='1XISRUkwtuK')
And if you don't want to use this method, you can always use force_login client method to login user without having to specify login nor password like that:
c.force_login(test_user)
I am trying to use data saved in django session variables to run a function once the webhook has confirmed that 'checkout.session.completed' but I always get a key error. I am 100% sure the keys exist in the session variables.
Here is my webhook:
#csrf_exempt
def stripe_webhook(request):
# You can find your endpoint's secret in your webhook settings
endpoint_secret = 'secret'
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
event = None
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
fulfull_order(session)
return HttpResponse(status=200)
Here is my fulfill order function:
def fulfull_order(session):
generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
/// send email code.
This line generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
Always gives a key error on request.session['goal'] The key definitely exists, it just seems it is inaccessible from the webhook view.
How to solve?
You should save the information you want to the metadata field when creating the checkout.Session.
def checkout(request):
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price': 'price_key',
'quantity': 1,
}],
mode='payment',
success_url=request.build_absolute_uri(reverse('success_url')) + '?session_id={CHECKOUT_SESSION_ID}',
cancel_url=request.build_absolute_uri(reverse('cancel_url')),
metadata={'someKeyHere': 'your session variable data'}
)
return JsonResponse({
'session_id' : session.id,
'stripe_public_key' : settings.STRIPE_PUBLISHABLE_KEY
})
then you can access the information like session['metadata']['someKeyHere']
The webhook event is a separate request coming directly from Stripe that would not be related to any Django session and so this lack of session data would seem expected. As #Anthony suggests you can store this information in the Checkout Session metadata when you create the session. The metadata will be included in the webhook object.
I wanna change the payload of verifyToken mutation. So instead of returning email I wanna return id or the whole object
schema.py
import graphql_jwt
class Query(AccountsQuery, ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
class Mutation( AccountsMutation, ObjectType):
# This class will inherit from multiple Mutations
# as we begin to add more apps to our project
token_auth = graphql_jwt.ObtainJSONWebToken.Field()
verify_token = graphql_jwt.Verify.Field()
refresh_token = graphql_jwt.Refresh.Field()
pass
schema = Schema(query=Query, mutation=Mutation)
I've seen this: https://django-graphql-jwt.domake.io/en/latest/_modules/graphql_jwt/utils.html#get_user_by_natural_key
But I dont know how I can implement that - if it's a correct approach
Any ideas how I could do that?
To modify the payload of your token, you have to create a custom jwt_payload function and you must add the path of the function to the JWT_PAYLOAD_HANDLER in your settings.GRAPHQL_JWT
projectname.settings.py
GRAPHQL_JWT = {
...
'JWT_PAYLOAD_HANDLER': 'polls.schema.jwt_payload',
...
}
polls.schema.py
import graphql_jwt
from datetime import datetime
from projectname.settings import GRAPHQL_JWT
def jwt_payload(user, context=None):
username = user.get_username()
payload = {
user.USERNAME_FIELD: username,
'user_id': user.id,
'email': user.email,
'phone': user.phone,
'exp': datetime.utcnow() + GRAPHQL_JWT['JWT_EXPIRATION_DELTA'],
...
}
original jwt payload function
Note that in your case the user.USERNAME_FIELD is your email field, it's the default field in your payload (it's also the field you need to get the token), you can remove it or you can edit it by following the django documentation, if you declare a custom USERNAME_FIELD you can choose from all columns in the 'auth_user' table, but you have to declare it bevor you run python manage.py migrate for the first time
i wanted to insert new data into porstgresql using odooRPc i am having error like below
RPCError: dictionary update sequence element #0 has length 1; 2 is required
my python script code is :
def POST(self):
data = []
web.header('Access-Control-Allow-Origin', '*')
web.header('Access-Control-Allow-Credentials', 'true')
web.header('Content-Type', 'application/json')
auth = web.input()
print("auth")
print(auth)
name=auth['username']
pwd=auth['password']
city=auth['city']
eml=auth['eml']
mobile=auth['phone']
state_id=auth['state']
country_id=auth['country']
# print(type(auth['country']))
# country_id=auth.get('Country').get('id')
# country_id=auth['country'].get('id')
# print(country_id)
# state_id=auth['state']
# print(state_id)
odoo = odoorpc.ODOO('field.holisticbs.com',port=8069)
odoo.login('field.holisticbs.com','info#holisticbs.com','admin')
# Customer = odoo.execute_kw('res.partner','create',{'name':name,' email':eml,'mobile':mobile,' country_id':country_id,'state_id':state_id})
Customer = odoo.execute_kw('res.partner','create',{'name':name,' email':eml,'mobile':mobile})
print(Customer)
# Users = odoo.env['res.partner']
# user = Users.browse([int(idu)])
# print(user)
# Customer = odoo.execute_kw('res.user','create',{'login':eml,' password':pwd})
return json.dumps(Customer)
I have made my comments as below , kindly request you to find it as below it will help in your case:
Well there are many RPC Library (Python) for connecting with the API of Odoo/OpenERP:
xmlrpclib
odoorpc
erppeek
oerplib
openerplib..
In Your case You have chose the odoorpc.
Here is the code snippet for using it odoorpc:
import odoorpc
import json
domain ='localhost' #the domain
port=8069 #the active port
username = 'username' #the user name
password = 'password' #the user password
dbname = 'database_name' #the database
#Validate the credentials
odoo = odoorpc.ODOO(domain, port=port)
odoo.login(dbname, username, password)
#Login User details
user = odoo.env.user
print(user.name) # user name
print(user.company_id.name) # user company name
#Create a partner
user_data = odoo.execute('res.partner', 'create',
{'name':"PRAKASH",'
email':" prakashsharmacs24#gmail.com",
'mobile':"7859884833"})
print(user_data)
But i have also find you are using the method execute_kw so please use xmlrpclib if you want to use method execute_kw
Here is the code snippet for using it xmlrpclib:
import xmlrpclib
domain ='localhost' #the domain
port=8069 #the active port
username = 'username' #the user name
password = 'password' #the user password
dbname = 'database_name' #the database
#Validate the credentials
url='http://{domain}:{port}'.format(domain=domain,port=port)
login_url='{url}/xmlrpc/2/common'.format(url=url)
sock_common = xmlrpclib.ServerProxy(login_url)
uid = sock_common.login(dbname, username, password)
print sock_common.version()
print uid
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
#Validate the access rights
print models.execute_kw(dbname, uid, password,
'res.partner', 'check_access_rights',
['read'], {'raise_exception': False})
#Execute the query
print models.execute_kw(dbname, uid, password,
'res.partner', 'search',
[[['is_company', '=', True], ['customer', '=', True]]])
You can also refer this Link for knowing the difference between the RPC library
I hope this will help you ..
With the following test, the token is not recognised as valid. In my manual test, it's working so I'm missing something in the way the password is generated I guess.
def test_actual_reset_password(self):
new_password = "myNewPassword012*"
token_generator = PasswordResetTokenGenerator()
user = UserFactory.create()
token = token_generator.make_token(user=user)
response = self.assert_page_loading(path="/forgot-password/reset/{0}/".format(token))
print response
# That loads the page with the error message mentioning that the token was already used
# So I cannot carry on:
form = response.form
form['new_password1'] = new_password
form['new_password2'] = new_password
response = form.submit()
In the django source code, in the PasswordResetForm, I've found this code; I can't see what the difference is:
def save(self, ..., token_generator=default_token_generator, ...):
"""
Generates a one-use only link for resetting password and sends to the
user.
"""
...
for user in self.users_cache:
...
c = {
...
'token': token_generator.make_token(user),
...
}
...
send_mail(subject, email, from_email, [user.email])
Ok, I was just searching for info on how to do this and your question prompted me to figure it out myself. I'm not sure if you're still working on this, but here's how I got it to work:
from django.core import mail
# First we get the initial password reset form.
# This is not strictly necessary, but I included it for completeness
response = self.c.get(reverse('password_reset'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template_name, 'authentication/password_reset_form.html')
# Then we post the response with our "email address"
response = self.c.post(reverse('password_reset'),{'email':'fred#home.com'})
self.assertEqual(response.status_code, 302)
# At this point the system will "send" us an email. We can "check" it thusly:
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Password reset on example.com')
# Now, here's the kicker: we get the token and userid from the response
token = response.context[0]['token']
uid = response.context[0]['uid']
# Now we can use the token to get the password change form
response = self.c.get(reverse('password_reset_confirm', kwargs={'token':token,'uidb64':uid}))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template_name, 'authentication/password_reset_confirm.html')
# Now we post to the same url with our new password:
response = self.c.post(reverse('password_reset_confirm',
kwargs={'token':token,'uidb36':uid}), {'new_password1':'pass','new_password2':'pass'})
self.assertEqual(response.status_code, 302)
And that's it! Not so hard after all.
This is how I did it for a functional test:
def test_password_reset_from_key(self):
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import base36_to_int, int_to_base36
user = User.objects.all()[:1].get()
token = default_token_generator.make_token(user)
self.get("/accounts/password/reset/key/%s-%s/" % (int_to_base36(user.id), token))
self.selenium.find_element_by_name("password1").send_keys("password")
self.selenium.find_element_by_name("password2").send_keys("password")
self.selenium.find_element_by_name("action").submit()
alert = self.selenium.find_element_by_css_selector(".alert-success")
self.assertIn('Password successfully changed.', alert.text)