Skip to content

Commit 6e27920

Browse files
dreese33abompard
authored andcommitted
Fix open redirect in login and logout urls
1 parent 8333a82 commit 6e27920

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

flask_oidc/views.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import logging
88
import warnings
9+
import re
910
from urllib.parse import urlparse
1011

1112
from authlib.integrations.base_client.errors import OAuthError
@@ -34,6 +35,30 @@
3435
auth_routes = Blueprint("oidc_auth", __name__)
3536

3637

38+
def validate_return_url(next, url_root):
39+
if next == url_root:
40+
return next
41+
if not re.match(r"^[a-zA-Z0-9:\/.\-@%?!&+#_=*~']{2,256}$", next):
42+
logger.debug("The redirect url you provided contains invalid characters")
43+
return url_root
44+
45+
temp_url = next
46+
if not next.startswith(('http://', 'https://')):
47+
# add a scheme for urlparse
48+
temp_url = 'http://' + next
49+
50+
parsed_url = urlparse(temp_url)
51+
parsed_root = urlparse(url_root)
52+
if not parsed_url.netloc and parsed_url.path.startswith('/'):
53+
# this is a valid relative url
54+
return next
55+
if parsed_url.netloc == parsed_root.netloc:
56+
# netloc should match for valid absolute urls
57+
return next
58+
logger.debug("The redirect url you provided is invalid")
59+
return url_root
60+
61+
3762
@auth_routes.route("/login", endpoint="login")
3863
def login_view():
3964
if current_app.config["OIDC_OVERWRITE_REDIRECT_URI"]:
@@ -44,7 +69,8 @@ def login_view():
4469
)
4570
else:
4671
redirect_uri = url_for("oidc_auth.authorize", _external=True)
47-
session["next"] = request.args.get("next", request.url_root)
72+
next = request.args.get("next", request.url_root)
73+
session["next"] = validate_return_url(next, request.url_root)
4874
before_login_redirect.send(
4975
g._oidc_auth,
5076
redirect_uri=redirect_uri,
@@ -99,7 +125,8 @@ def logout_view():
99125
flash("Your session expired, please reconnect.")
100126
else:
101127
flash("You were successfully logged out.")
102-
return_to = request.args.get("next", request.url_root)
128+
next = request.args.get("next", request.url_root)
129+
return_to = validate_return_url(next, request.url_root)
103130
after_logout.send(g._oidc_auth, reason=reason, return_to=return_to)
104131
return redirect(return_to)
105132

tests/test_views.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
before_authorize,
1818
before_logout,
1919
)
20+
from flask_oidc.views import validate_return_url
2021

2122
HAS_MULTIPLE_CONTEXT_MANAGERS = sys.hexversion >= 0x030900F0 # 3.9.0
2223

@@ -132,3 +133,22 @@ def test_oidc_callback_route(test_app, client, dummy_token):
132133
resp = client.get("/oidc_callback?state=dummy-state&code=dummy-code")
133134
assert resp.status_code == 302
134135
assert resp.location == "/authorize?state=dummy-state&code=dummy-code"
136+
137+
138+
def test_logout_return_url_invalid(client, dummy_token):
139+
with client.session_transaction() as session:
140+
session["oidc_auth_token"] = dummy_token
141+
session["oidc_auth_profile"] = {"nickname": "dummy"}
142+
response = client.get("/logout?next=https://www.google.com")
143+
assert response.status_code == 302
144+
assert response.location == "http://localhost/"
145+
146+
147+
def test_validate_return_url():
148+
url_root = "http://localhost/"
149+
valid = ["/test/url", "http://localhost/", "http://localhost/test/url"]
150+
invalid = ["test/url", "http://localhost1/", "https://www.google.com"]
151+
for valid_url in valid:
152+
assert validate_return_url(valid_url, url_root) == valid_url
153+
for invalid_url in invalid:
154+
assert validate_return_url(invalid_url, url_root) == url_root

0 commit comments

Comments
 (0)