Config
app.config['JWT_COOKIE_CSRF_PROTECT'] = True
app.config["JWT_TOKEN_LOCATION"] = ['cookies']
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=0.005)
class LogOut(Resource):
#jwt_required()
def get(self):
response = make_response(render_template('home.html', is_login=False))
unset_jwt_cookies(response)
return response
#app.after_request
def refresh_expiring_jwts(response):
try:
exp_timestamp = get_jwt()["exp"]
now = datetime.now(timezone.utc)
target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
if target_timestamp > exp_timestamp:
access_token = create_access_token(identity=get_jwt_identity())
set_access_cookies(response, access_token)
return response
except (RuntimeError, KeyError):
# Case where there is not a valid JWT. Just return the original respone
return response
I am trying to write code for implict refresh of access tokens using Flask-JWT-Extended library and having the below error.
In the above code login is working properly but if we access the logout the access token cookies are not being deleted.
Can anyone explain why it is not working?
Related
i am having trouble trying to mock test the on-boarding process of stripe connect. I am just learning how to use mock and i am struggling with the StripeAuthorizeCallbackView. the process is as follows: A user reaches the StripeAuthorizeView which sends them to the stripe api to sign up for an account. Once they successfully sign up for an account their redirected back to my platform and stripe sends a temporary code which i then send back to stripe with my api keys. Once i have sent the information back to stripe they then return me credentials for the user being the stripe_user_id.
Here is the two views in question:
import urllib
import requests
class StripeAuthorizeView(LoginRequiredMixin, View):
def get(self, request):
url = 'https://connect.stripe.com/express/oauth/authorize?'
user = self.request.user
if user.account_type == 'Business':
business_type = 'company'
else:
business_type = 'individual'
params = {
'response_type': 'code',
'scope': 'read_write',
'client_id': settings.STRIPE_CONNECT_CLIENT_ID,
'redirect_uri': f'http://127.0.0.1:8000/accounts/stripe/oauth/callback',
'stripe_user[email]' : user.email,
'stripe_user[business_type]' : business_type,
'stripe_user[url]' : 'http://127.0.0.1:8000/accounts/user/%s/' %user.pk,
}
url = f'{url}?{urllib.parse.urlencode(params)}'
return redirect(url)
lass StripeAuthorizeCallbackView(LoginRequiredMixin, View):
def get(self, request):
code = request.GET.get('code')
if code:
data = {
'client_secret': settings.STRIPE_SECRET_KEY,
'grant_type': 'authorization_code',
'client_id': settings.STRIPE_CONNECT_CLIENT_ID,
'code': code
}
url = 'https://connect.stripe.com/oauth/token'
resp = requests.post(url, params=data)
stripe_user_id = resp.json()['stripe_user_id']
stripe_access_token = resp.json()['access_token']
stripe_refresh_token = resp.json()['refresh_token']
user = self.request.user
user.stripe_access_token = stripe_access_token
user.stripe_user_id = stripe_user_id
user.stripe_refresh_token = stripe_refresh_token
user.save()
notify.send(sender=user, recipient=user,
verb='You have succesfully linked a stripe account. You can now take payments for sales.',
level='info')
redirect_url = reverse('account', kwargs={'pk': user.pk})
response = redirect(redirect_url)
return response
else:
user = self.request.user
notify.send(sender=user, recipient=user,
verb='Your attempt to link a stripe account failed. Please contact customer support.',
level='warning')
url = reverse('account', kwargs={'pk': user.pk})
response = redirect(url)
return response
I am not very worried about testing the StripeAuthorizeView a lot. I am more trying to figure out how to test the StripeAuthorizeCallbackView. All i can figure out is that i will need to mock both the code returned and then mock the following requests.post. This test is important to confirm my platform is linking the users credentials after the on-boarding process. Any help on this will be greatly appricated.
edit:
So far i have the following :
#classmethod
def setUpTestData(cls):
cls.test_user = User.objects.create_user(
password='test',
full_name='test name',
email='test#test.com',
address='1 test st',
suburb='test',
state='NSW',
post_code='2000',
contact_number='0433335333' )
#patch('requests.get')
def test_authorizecallback_creates_stripe_details(self, get_mock):
code = requests.get('code')
user = self.test_user
self.client.login(email='test#test.com', password='test')
mocked = ({'stripe_user_id' : '4444','stripe_access_token' : '2222',
'stripe_refresh_token' : '1111' })
with mock.patch('requests.post', mock.Mock(return_value=mocked)):
response = self.client.get('/accounts/stripe/oauth/callback/',
{'code' : '1234'})
self.assertEqual(user.stripe_access_token, '222')
message = list(response.context.get('messages'))[0]
however i keep getting:
File "C:\Users\typef\Desktop\Projects\python_env\fox-listed\Fox-Listed\fox-listed\user\views.py", line 142, in get
stripe_user_id = resp.json()['stripe_user_id']
AttributeError: 'dict' object has no attribute 'json'
the actual response that the StripeAuthorizeCallBackView gives is:
{'access_token': 'sk_test_1KyTG74Ouw65KYTR1O03WjNA00viNjcIfO', 'livemode': False, 'refresh_token': 'rt_H3Vrhd0XbSH7zbmqfDyMNwolgt1Gd7r4ESBDBr5a4VkCzTRT', 'token_type': 'bearer', 'stripe_publishable_key': 'pk_test_**********', 'stripe_user_id': 'acct_1GVOpAF7ag87i2I6', 'scope': 'express'}
Looks like i got it, if there is a flaw here let me know but here is what i have:
class TestStripeAuthorizeCallbackView:
#patch('user.views.requests')
def test_authorizecallback_creates_stripe_details(self, requests_mock):
json = { 'stripe_user_id' : '4444', 'access_token' : '2222', 'refresh_token' : '1111'}
requests_mock.post.return_value.json.return_value = json
user = mixer.blend('user.CustomUser', stripe_user_id=None, access_token=None, refresh_token=None)
req = RequestFactory().get('/', data={'code' : '1234'})
middleware = SessionMiddleware()
middleware.process_request(req)
req.session.save()
messages = FallbackStorage(req)
setattr(req, '_messages', messages)
req.user = user
resp = StripeAuthorizeCallbackView.as_view()(req)
assert resp.status_code == 302 ,'should redirect to success url'
assert user.stripe_user_id == '4444', 'should assign stripe_user_id to user'
assert user.stripe_access_token == '2222', 'should assign an access_token'
assert user.stripe_refresh_token == '1111', 'should assign a refresh_token'
What you're describing isn't mocking so much as it is end-to-end testing, connecting actual test accounts, which you can do.
As long as you're using a test client_id then when you are redirected to Stripe to create the account you can skip the form via a link and get directed back to your site with a real (test mode) oauth code.
Essentially you can set this up and actually go through the flow to create & connect new disposable test Stripe accounts.
#app.route('/login')
def showLogin():
state = ''.join(random.choice(string.ascii_uppercase + string.digits)
for x in xrange(32))
login_session['state'] = state
# return "The current session state is %s" % login_session['state']
return render_template('login.html', STATE=state)
#app.route('/gconnect', methods=['POST'])
def gconnect():
# Validate state token
if request.args.get('state') != login_session['state']:
response = make_response(json.dumps('Invalid state parameter.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Obtain authorization code
code = request.data
try:
# Upgrade the authorization code into a credentials object
oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
credentials = oauth_flow.step2_exchange(code)
except FlowExchangeError:
response = make_response(
json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Check that the access token is valid.
access_token = credentials.access_token
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s'
% access_token)
h = httplib2.Http()
result = json.loads(h.request(url, 'GET')[1])
# If there was an error in the access token info, abort.
if result.get('error') is not None:
response = make_response(json.dumps(result.get('error')), 500)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is used for the intended user.
gplus_id = credentials.id_token['sub']
if result['user_id'] != gplus_id:
response = make_response(
json.dumps("Token's user ID doesn't match given user ID."), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Verify that the access token is valid for this app.
if result['issued_to'] != CLIENT_ID:
response = make_response(
json.dumps("Token's client ID does not match app's."), 401)
print "Token's client ID does not match app's."
response.headers['Content-Type'] = 'application/json'
return response
stored_access_token = login_session.get('access_token')
stored_gplus_id = login_session.get('gplus_id')
if stored_access_token is not None and gplus_id == stored_gplus_id:
response = make_response(json.dumps('Current user is connected'), 200)
response.headers['Content-Type'] = 'application/json'
return response
# Store the access token in the session for later use.
login_session['access_token'] = credentials.access_token
login_session['gplus_id'] = gplus_id
# Get user info
userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo"
params = {'access_token': credentials.access_token, 'alt': 'json'}
answer = requests.get(userinfo_url, params=params)
data = answer.json()
login_session['username'] = data['name']
login_session['picture'] = data['picture']
login_session['email'] = data['email']
# See if a user exists, if it doesn't make a new one
user_id = getUserID(login_session['email'])
if not user_id:
user_id = createUser(login_session)
login_session['user_id'] = user_id
output = ''
output += '<h1>Welcome, '
output += login_session['username']
output += '!</h1>'
output += '<img src="'
output += login_session['picture']
output += ' " style = "width: 300px; height: 300px;border-radius: 150px;'
output += '-webkit-border-radius: 150px;-moz-border-radius: 150px;"> '
print "done!"
return output
I ran this OAuth code on my local machine. It worked but on Amazon Lightsail instance it gave the following error:
*Error: invalid_request Permission denied to generate login hint for target domain.
I even corrected my Redirect Uris and JAVA Script Origins to .com but it still gives the same error even on erasing the cookies.
When I use Google OAuth to verify my user, After verify is passed, I want to redirect to the page which user visit before authority, So I want to save the page path to user's cookie, so I implementation like this:
def get_login_resp(request, redirect):
print(redirect)
auth_url = "https://accounts.google.com/o/oauth2/auth?" + urlencode({
"client_id": GOOGLE_CLIENT_ID,
"response_type": "code",
"redirect_uri": make_redirect_url(request, redirect),
"scope": "profile email",
"max_auth_age": 0
})
resp = HttpResponseRedirect(auth_url)
max_age = 3600 * 24
expires = datetime.strftime(datetime.utcnow() + timedelta(seconds=max_age), "%a, %d-%b-%Y %H:%M:%S GMT")
print(expires)
resp.set_cookie('google_auth_redirect', redirect, max_age=max_age, expires=expires,
domain=LOGIN_COOKIE_DOMAIN, secure=True, httponly=True)
print(resp._headers)
print(resp.cookies)
return resp
ps: redirect is the page path which I want to save
But when request the login url with Postman, I can only see this headers:
response headers
And these cookies:
Cookies
So how can i do with this problem? There is not any error info for me.
Try every methods to find out what's wrong, But still failed.
So I try to run server on an other machine(a Linux server), it works!!!
BTW: My develop PC is Macbook Pro 15-inch, 2017 with macOS High Sierra 10.13.1
Update at 14/Jan/2020:
Didn't find the root cause, but I solved this issue by saving redirect_url to session data, in this solution you should check auth valid by using another request, then call google auth to reauth again, code like below:
class GoogleAuthView(RedirectView):
# google auth view
def get(self, request, *args, **kwargs):
# get redirect url from url params, frontend code should pass the param in request url
redirect_url = request.GET.get('redirect_url', None)
if redirect_url:
redirect_url = parse.unquote(redirect_url)
credentials = request.session.get("credentials", None)
if (not credentials) or ('expire_time' not in credentials) or (credentials['expire_time'] < time.time()):
request.session['redirect_url'] = redirect_url # if need google auth, save redirect url to session first
else:
if redirect_url:
return HttpResponseRedirect(redirect_url)
flow = google_auth_oauthlib.flow.Flow.from_client_config(
client_config=settings.GOOGLE_AUTH_CONFIG,
scopes=settings.GOOGLE_AUTH_SCOPES
)
flow.redirect_uri = settings.GOOGLE_AUTH_CONFIG['web']['redirect_uris'][0]
authorization_url, state = flow.authorization_url(
access_type='offline',
include_granted_scopes='true'
)
request.session['state'] = state
return HttpResponseRedirect(authorization_url)
class GoogleAuthCallBackView(BasicView):
# google callback view
def get(self, request, *args, **kwargs):
state = request.session.get('state')
flow = google_auth_oauthlib.flow.Flow.from_client_config(
client_config=settings.GOOGLE_AUTH_CONFIG,
scopes=settings.GOOGLE_AUTH_SCOPES,
state=state
)
flow.redirect_uri = settings.GOOGLE_AUTH_CONFIG['web']['redirect_uris'][0]
# get redirect url from session data if exists
redirect_url = request.session.get('redirect_url') or settings.ADMIN_LOGIN_REDIRECT_URL
response = HttpResponseRedirect(redirect_url)
try:
del request.session['redirect_url']
except KeyError:
logger.info('Delete `redirect_url` in session get KeyError.')
pass
try:
flow.fetch_token(authorization_response=request.build_absolute_uri())
except Exception as e:
logger.error(e.message)
return response
# save credentials to session
credentials = flow.credentials
request.session["credentials"] = {
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes,
'expire_time': time.time() + TOKEN_EXPIRE_TIME,
}
profile_client = googleapiclient.discovery.build(
serviceName='oauth2',
version='v2',
credentials=credentials
)
profile = profile_client.userinfo().v2().me().get().execute()
email = profile['email']
user = user_manager.get_user_by_email(email)
if user:
user.username = profile['name'] # sync username from google
user.picture = profile['picture'] # sync avatar from google
user.save()
request.session["user"] = user.to_dict()
else:
return HttpResponseRedirect("/api/non_existent_user/") # show non-existent user
return response
I'm trying to get an access token for explara API using an Django application using the following code. It's working fine for getting code. After getting code when it goes to else part of get_explara_token view; give the error '{"error":"invalid_request","error_description":"The grant type was not specified in the request"}' while I has defined grant type as authorization_code as mentioned in Explara API documentation here http://developers.explara.com/get-api-access
views.py
import requests
import urllib
def get_explara_token(request):
access_code = request.GET.get('code')
if access_code is None:
code = 'code'
state = 'event'
query = {
'response_type':code,
'client_id': EXPLARA_CLIENT_ID,
'state':state
}
url = 'https://account.explara.com/account/oauth/authorize?%s' % urllib.urlencode(query)
return HttpResponseRedirect(url)
else:
url = 'https://account.explara.com/account/oauth/token/' + 'client_id=' + str(EXPLARA_CLIENT_ID) + '&client_secret=' + str(EXPLARA_CLIENT_SECRET) + '&grant_type=' + 'authorization_code' + '&code=' + str(access_code)
response = requests.post(url)
return HttpResponse(response)
What am I doing wrong?
You should post data in that way:
requests.post('https://account.explara.com/account/oauth/token', data={'grant_type': 'authorization_code', 'client_id': 123123123, 'client_secret': '123123123', 'code': '123123'}).content
Instead of that you tried to pass data via GET parameters.
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)