Skip to content

Commit eb35b80

Browse files
dpgasparbetodealmeida
authored andcommitted
fix: url shortener invalid input (#13461)
* fix: url shortner invalid input * fix lint
1 parent e8162cb commit eb35b80

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

superset/views/redirects.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17+
import logging
18+
from typing import Optional
19+
1720
from flask import flash, request, Response
1821
from flask_appbuilder import expose
1922
from flask_appbuilder.security.decorators import has_access_api
@@ -24,11 +27,22 @@
2427
from superset.typing import FlaskResponse
2528
from superset.views.base import BaseSupersetView
2629

30+
logger = logging.getLogger(__name__)
31+
2732

2833
class R(BaseSupersetView): # pylint: disable=invalid-name
2934

3035
"""used for short urls"""
3136

37+
@staticmethod
38+
def _validate_url(url: Optional[str] = None) -> bool:
39+
if url and (
40+
url.startswith("//superset/dashboard/")
41+
or url.startswith("//superset/explore/")
42+
):
43+
return True
44+
return False
45+
3246
@event_logger.log_this
3347
@expose("/<int:url_id>")
3448
def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use
@@ -38,8 +52,9 @@ def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use
3852
if url.url.startswith(explore_url):
3953
explore_url += f"r={url_id}"
4054
return redirect(explore_url[1:])
41-
42-
return redirect(url.url[1:])
55+
if self._validate_url(url.url):
56+
return redirect(url.url[1:])
57+
return redirect("/")
4358

4459
flash("URL to nowhere...", "danger")
4560
return redirect("/")
@@ -49,6 +64,9 @@ def index(self, url_id: int) -> FlaskResponse: # pylint: disable=no-self-use
4964
@expose("/shortner/", methods=["POST"])
5065
def shortner(self) -> FlaskResponse: # pylint: disable=no-self-use
5166
url = request.form.get("data")
67+
if not self._validate_url(url):
68+
logger.warning("Invalid URL: %s", url)
69+
return Response(f"Invalid URL: {url}", 400)
5270
obj = models.Url(url=url)
5371
db.session.add(obj)
5472
db.session.commit()

tests/core_tests.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,28 @@ def test_shortner(self):
634634
resp = self.client.post("/r/shortner/", data=dict(data=data))
635635
assert re.search(r"\/r\/[0-9]+", resp.data.decode("utf-8"))
636636

637+
def test_shortner_invalid(self):
638+
self.login(username="admin")
639+
invalid_urls = [
640+
"hhttp://invalid.com",
641+
"hhttps://invalid.com",
642+
"www.invalid.com",
643+
]
644+
for invalid_url in invalid_urls:
645+
resp = self.client.post("/r/shortner/", data=dict(data=invalid_url))
646+
assert resp.status_code == 400
647+
648+
def test_redirect_invalid(self):
649+
model_url = models.Url(url="hhttp://invalid.com")
650+
db.session.add(model_url)
651+
db.session.commit()
652+
653+
self.login(username="admin")
654+
response = self.client.get(f"/r/{model_url.id}")
655+
assert response.headers["Location"] == "http://localhost/"
656+
db.session.delete(model_url)
657+
db.session.commit()
658+
637659
@skipUnless(
638660
(is_feature_enabled("KV_STORE")), "skipping as /kv/ endpoints are not enabled"
639661
)

0 commit comments

Comments
 (0)