Skip to content

Commit

Permalink
Merge pull request #155 from level12/153-relative-redirect-urls
Browse files Browse the repository at this point in the history
use relative urls for redirects
  • Loading branch information
guruofgentoo committed Jul 8, 2022
2 parents 0fe10aa + d69de3a commit 6d6f959
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 92 deletions.
2 changes: 1 addition & 1 deletion keg_auth/model/__init__.py
Expand Up @@ -491,7 +491,7 @@ class BundleMixin(object):
@might_flush
@classmethod
def edit(cls, oid=None, **kwargs):
obj = super(BundleMixin, cls).edit(oid=oid, _commit=False, **kwargs)
obj = super(BundleMixin, cls).edit(oid, _commit=False, **kwargs)
return obj


Expand Down
131 changes: 47 additions & 84 deletions keg_auth/testing.py
Expand Up @@ -3,7 +3,7 @@

from datetime import timedelta
from unittest import mock
from urllib.parse import quote, urlparse
from urllib.parse import quote

import arrow
import flask
Expand Down Expand Up @@ -31,6 +31,10 @@ class AuthAttemptTests(object):
"""Tests to verify that automated attempt logging/blocking works as intended. These
tests are included in the AuthTests class and are intended to be used in target
applications to verify customization hasn't broken basic KegAuth functionality."""
login_url = None
forgot_password_url = None
reset_password_url = None

forgot_invalid_flashes = [('error', 'No user account matches: foo@bar.com')]
forgot_lockout_flashes = [('error', 'Too many failed attempts.')]
forgot_success_flashes = [
Expand Down Expand Up @@ -59,8 +63,7 @@ def setup_class(cls):
cls.bundle_ent = flask.current_app.auth_manager.entity_registry.bundle_cls

def do_login(self, client, email, password, submit_status=200):
login_url = flask.url_for(flask.current_app.auth_manager.endpoint('login'))
resp = client.get(login_url)
resp = client.get(self.login_url)
resp.form['login_id'] = email
resp.form['password'] = password
return resp.form.submit(status=submit_status)
Expand Down Expand Up @@ -307,8 +310,7 @@ def do_test(username, login_time, flashes, submit_status=200, client=client):
client=self.client)

def do_forgot(self, client, email, submit_status=200):
forgot_url = flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password'))
resp = client.get(forgot_url)
resp = client.get(self.forgot_password_url)
resp.form['email'] = email
return resp.form.submit(status=submit_status)

Expand Down Expand Up @@ -473,8 +475,7 @@ def do_reset_test(self, user, reset_time, flashes, submit_status=200):
return_value=reset_time,
):
token = user.token_generate()
url = flask.url_for(flask.current_app.auth_manager.endpoint('reset-password'),
user_id=user.id, token=token)
url = self.reset_password_url.format(user_id=user.id, token=token)
resp = self.client.get(url, status=200)
new_pass = randchars(8)
resp.form['password'] = new_pass
Expand Down Expand Up @@ -595,6 +596,12 @@ class AuthTests(AuthAttemptTests):
These tests are designed so they can can be imported into an application's tests
and ran to ensure customization of KegAuth hasn't broken basic functionality.
"""
login_url = '/login'
forgot_password_url = '/forgot-password'
reset_password_url = '/reset-password/{user_id}/{token}'
after_login_url = '/'
logout_url = '/logout'
after_logout_url = login_url
protected_url = '/secret1'
protected_url_permissions = None

Expand All @@ -605,19 +612,16 @@ def setup(self):
def test_login_get(self):
app = flask.current_app
client = flask_webtest.TestApp(app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)
assert resp.status_code == 200

def test_login_head(self):
client = flask_webtest.TestApp(flask.current_app)
client.head(
flask.url_for(flask.current_app.auth_manager.endpoint('login')),
status=405,
)
client.head(self.login_url, status=405)

def test_login_form_error(self):
client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo'
resp = resp.form.submit(status=200)
Expand All @@ -632,16 +636,14 @@ def test_login_field_success(self):
user = self.user_ent.testing_create(email='foo@bar.com', password='pass')

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'pass'
resp = resp.form.submit()

assert resp.status_code == 302, resp.html
assert resp.headers['Location'] == flask.url_for(
flask.current_app.auth_manager.endpoint('after-login')
)
assert resp.headers['Location'] == self.after_login_url
flash_success = flask.current_app.auth_manager.login_authenticator_cls.responder_cls['login'].flash_success # noqa
category = flash_success[1]
message = flash_success[0]
Expand All @@ -660,16 +662,14 @@ def test_login_field_success_next_parameter(self):

next = '/foo'
client = flask_webtest.TestApp(flask.current_app)
resp = client.get('{}?next={}'.format(
flask.url_for(flask.current_app.auth_manager.endpoint('login')), next)
)
resp = client.get('{}?next={}'.format(self.login_url, next))

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'pass'
resp = resp.form.submit()

assert resp.status_code == 302, resp.html
assert resp.headers['Location'] == 'http://keg.example.com{}'.format(next)
assert resp.headers['Location'] == next
flash_success = flask.current_app.auth_manager.login_authenticator_cls.responder_cls['login'].flash_success # noqa
category = flash_success[1]
message = flash_success[0]
Expand All @@ -683,14 +683,14 @@ def test_login_field_success_next_session(self):
client = flask_webtest.TestApp(flask.current_app)
with client.session_transaction() as sess:
sess['next'] = next
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'pass'
resp = resp.form.submit()

assert resp.status_code == 302, resp.html
assert resp.headers['Location'] == 'http://keg.example.com{}'.format(next)
assert resp.headers['Location'] == next
flash_success = flask.current_app.auth_manager.login_authenticator_cls.responder_cls['login'].flash_success # noqa
category = flash_success[1]
message = flash_success[0]
Expand All @@ -703,43 +703,31 @@ def test_next_parameter_not_open_redirect(self):
# unquoted next parameter
next = 'http://www.example.com'
client = flask_webtest.TestApp(flask.current_app)
resp = client.get('{}?next={}'.format(
flask.url_for(flask.current_app.auth_manager.endpoint('login')), next
)
)
resp = client.get('{}?next={}'.format(self.login_url, next))

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'pass'
resp = resp.form.submit()

assert resp.status_code == 302, resp.html
# verify the 'next' parameter was ignored
assert resp.headers['Location'] == flask.url_for(
flask.current_app.auth_manager.endpoint('after-login')
)
assert resp.headers['Location'] == self.after_login_url
flash_success = flask.current_app.auth_manager.login_authenticator_cls.responder_cls['login'].flash_success # noqa
category = flash_success[1]
message = flash_success[0]
assert resp.flashes == [(category, message)]

# quoted next parameter
client = flask_webtest.TestApp(flask.current_app)
resp = client.get(
'{}?next={}'.format(
flask.url_for(flask.current_app.auth_manager.endpoint('login')),
quote(next)
)
)
resp = client.get('{}?next={}'.format(self.login_url, quote(next)))

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'pass'
resp = resp.form.submit()

assert resp.status_code == 302, resp.html
# verify the 'next' parameter was ignored
assert resp.headers['Location'] == flask.url_for(
flask.current_app.auth_manager.endpoint('after-login')
)
assert resp.headers['Location'] == self.after_login_url
flash_success = flask.current_app.auth_manager.login_authenticator_cls.responder_cls['login'].flash_success # noqa
category = flash_success[1]
message = flash_success[0]
Expand All @@ -751,7 +739,7 @@ def test_login_invalid_password(self):
assert self.attempt_ent.query.count() == 0

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'badpass'
Expand All @@ -773,7 +761,7 @@ def test_login_invalid_password(self):

def test_login_user_missing(self):
client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'badpass'
Expand All @@ -789,7 +777,7 @@ def test_login_user_unverified(self):
self.user_ent.testing_create(email='foo@bar.com', password='pass', is_verified=False)

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'badpass'
Expand All @@ -805,7 +793,7 @@ def test_login_user_disabled(self):
self.user_ent.testing_create(email='foo@bar.com', password='pass', is_enabled=False)

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('login')))
resp = client.get(self.login_url)

resp.form['login_id'] = 'foo@bar.com'
resp.form['password'] = 'badpass'
Expand All @@ -824,10 +812,7 @@ def test_login_protection(self):

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(self.protected_url, status=302)
full_login_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('login'))).path
)
assert resp.headers['Location'].startswith(full_login_url)
assert resp.headers['Location'].startswith(self.login_url)

resp = resp.follow()
resp.form['login_id'] = 'foo@bar.com'
Expand All @@ -843,7 +828,7 @@ def test_login_protection(self):

def test_forgot_pw_form_error(self):
client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password')))
resp = client.get(self.forgot_password_url)
resp = resp.form.submit(status=200)

flash_form_error = flask.current_app.auth_manager.login_authenticator_cls.\
Expand All @@ -854,7 +839,7 @@ def test_forgot_pw_form_error(self):

def test_forgot_pw_invalid_user(self):
client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password')))
resp = client.get(self.forgot_password_url)

resp.form['email'] = 'foo@bar.com'
resp = resp.form.submit(status=200)
Expand All @@ -869,7 +854,7 @@ def test_forgot_pw_user_disabled(self):
self.user_ent.testing_create(email='foo@bar.com', password='pass', is_enabled=False)

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password')))
resp = client.get(self.forgot_password_url)

resp.form['email'] = 'foo@bar.com'
resp = resp.form.submit(status=200)
Expand All @@ -884,7 +869,7 @@ def test_forgot_pw_success(self):
self.user_ent.testing_create(email='foo@bar.com')

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password')))
resp = client.get(self.forgot_password_url)

resp.form['email'] = 'foo@bar.com'
resp = resp.form.submit(status=302)
Expand All @@ -895,18 +880,14 @@ def test_forgot_pw_success(self):
message = flash_success[0]
assert resp.flashes == [(category, message)]

full_login_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('login'))).path
)
assert resp.headers['Location'] == full_login_url
assert resp.headers['Location'] == self.login_url

def test_reset_pw_success(self):
user = self.user_ent.testing_create()
if has_attempt_model:
assert self.attempt_ent.query.count() == 0
token = user.token_generate()
url = flask.url_for(flask.current_app.auth_manager.endpoint('reset-password'),
user_id=user.id, token=token)
url = self.reset_password_url.format(user_id=user.id, token=token)

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(url, status=200)
Expand All @@ -921,19 +902,15 @@ def test_reset_pw_success(self):
message = flash_success[0]
assert resp.flashes == [(category, message)]

full_login_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('login'))).path
)
assert resp.headers['Location'] == full_login_url
assert resp.headers['Location'] == self.login_url
if has_attempt_model:
assert self.attempt_ent.query.count() == 1
assert self.attempt_ent.get_by(attempt_type='reset', user_input=user.email)

def test_reset_pw_form_error(self):
user = self.user_ent.testing_create()
token = user.token_generate()
url = flask.url_for(flask.current_app.auth_manager.endpoint('reset-password'),
user_id=user.id, token=token)
url = self.reset_password_url.format(user_id=user.id, token=token)

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(url, status=200)
Expand All @@ -946,16 +923,14 @@ def test_reset_pw_form_error(self):
assert resp.flashes == [(category, message)]

def test_reset_pw_missing_user(self):
url = flask.url_for(flask.current_app.auth_manager.endpoint('reset-password'),
user_id='99999999', token='123')
url = self.reset_password_url.format(user_id='99999999', token='123')

client = flask_webtest.TestApp(flask.current_app)
client.get(url, status=404)

def test_reset_pw_bad_token(self):
user = self.user_ent.testing_create()
url = flask.url_for(flask.current_app.auth_manager.endpoint('reset-password'),
user_id=user.id, token='abc')
url = url = self.reset_password_url.format(user_id=user.id, token='abc')

client = flask_webtest.TestApp(flask.current_app)
resp = client.get(url, status=302)
Expand All @@ -966,10 +941,7 @@ def test_reset_pw_bad_token(self):
message = flash_invalid_token[0]
assert resp.flashes == [(category, message)]

full_forgot_password_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password'))).path
)
assert resp.headers['Location'] == full_forgot_password_url
assert resp.headers['Location'] == self.forgot_password_url

def test_verify_account_success(self):
user = self.user_ent.testing_create(is_verified=False)
Expand All @@ -991,10 +963,7 @@ def test_verify_account_success(self):
message = flash_success[0]
assert resp.flashes == [(category, message)]

full_login_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('login'))).path
)
assert resp.headers['Location'] == full_login_url
assert resp.headers['Location'] == self.login_url

assert user.is_verified

Expand Down Expand Up @@ -1034,10 +1003,7 @@ def test_verify_account_bad_token(self):
message = flash_invalid_token[0]
assert resp.flashes == [(category, message)]

full_forgot_password_url = 'http://keg.example.com{}'.format(
urlparse(flask.url_for(flask.current_app.auth_manager.endpoint('forgot-password'))).path
)
assert resp.headers['Location'] == full_forgot_password_url
assert resp.headers['Location'] == self.forgot_password_url

def test_logout(self):
user = self.user_ent.testing_create(permissions=self.protected_url_permissions)
Expand All @@ -1049,14 +1015,11 @@ def test_logout(self):
client.get(self.protected_url, status=200)

# logout
resp = client.get(flask.url_for(flask.current_app.auth_manager.endpoint('logout')),
status=302)
resp = client.get(self.logout_url, status=302)
assert resp.flashes == [('success', 'You have been logged out.')]

# Check redirect location
full_after_logout_url = flask.url_for(flask.current_app.auth_manager.
endpoint('after-logout'))
assert resp.headers['Location'] == full_after_logout_url
assert resp.headers['Location'] == self.after_logout_url

# Confirm logout occured
client.get(self.protected_url, status=302)
Expand Down

0 comments on commit 6d6f959

Please sign in to comment.