codeant-ai-for-open-source[bot] commented on code in PR #39469:
URL: https://github.com/apache/superset/pull/39469#discussion_r3474349712
##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
rv = self.client.put("/api/v1/me/", json={})
assert rv.status_code == 400
+ def test_update_me_rejects_password_when_auth_db(self) -> None:
+ """Reject password changes via PUT /me with a 400 when AUTH_TYPE is
AUTH_DB."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(meUri, json={"password": "ignored"})
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_put_my_password_wrong_current(self) -> None:
+ """Reject the change with a 400 when current_password is incorrect."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": "not-the-admin-password",
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["message"] == "Incorrect current password."
+
+ def test_put_my_password_weak_new(self) -> None:
+ """Verify a new password failing policy is rejected with a 400."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "short",
+ "confirm_password": "short",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "new_password" in data["message"]
+
+ def test_put_my_password_success(self) -> None:
+ """Cover the successful password change flow for the current user."""
+ self.login(ADMIN_USERNAME)
+ new_password = "AnotherStr0ng!Pass" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ rv2 = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": new_password,
+ "new_password": "YetAnotherStr0ng!Pw",
+ "confirm_password": "YetAnotherStr0ng!Pw",
+ },
+ )
+ assert rv2.status_code == 200
+ finally:
+ self._restore_admin_default_password()
+
+ def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+ """
+ Rotating the session stamp on password change logs out other clients
that
+ still present the pre-change signed session cookie.
+ """
+ from flask.testing import FlaskClient
+
+ app = superset_integration_app
+ client_a: FlaskClient = app.test_client()
+ login(client_a, ADMIN_USERNAME)
+
+ session_cookie = client_a.get_cookie("session")
+ assert session_cookie is not None
+
+ client_b: FlaskClient = app.test_client()
+ client_b.set_cookie(
+ key="session",
+ value=session_cookie.value,
+ domain=session_cookie.domain or "localhost",
+ path=session_cookie.path or "/",
+ )
+
+ new_password = "AnotherStr0ng!PassClone" # noqa: S105
+ try:
+ rv = client_a.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ assert client_b.get(meUri).status_code == 401
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_clears_remember_cookie(self) -> None:
+ """
+ Password change schedules Flask-Login remember-me cookie deletion.
+
+ Superset does not expose remember-me in the React login UI; this is
defensive
+ hardening for FAB / Flask-Login persistent cookies.
+ """
+ app = superset_integration_app
+ remember_name = app.config.get("REMEMBER_COOKIE_NAME",
"remember_token")
+ self.login(ADMIN_USERNAME)
+ self.client.set_cookie(remember_name, "stale-remember-token")
+
+ new_password = "AnotherStr0ng!PassRemember" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ set_cookies = rv.headers.getlist("Set-Cookie")
+ cleared = any(
+ remember_name in header
+ and ("=;" in header or "Max-Age=0" in header or "1970" in
header)
+ for header in set_cookies
+ )
+ assert cleared, f"Expected remember cookie clear in {set_cookies}"
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_invalid_hash_algorithm(self) -> None:
Review Comment:
**Suggestion:** Add a short docstring to this newly added test method to
describe the invalid hash algorithm scenario and expected behavior.
[custom_rule]
**Severity Level:** Minor ⚠️
<details>
<summary><b>Why it matters? 🤔 </b></summary>
This newly added test method has no docstring in the final file state.
The rule requires newly added Python functions and classes to include
docstrings, so the suggestion correctly identifies a real violation.
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=4310b6f95dc64352b384c9c47c489197&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=4310b6f95dc64352b384c9c47c489197&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** tests/integration_tests/users/api_tests.py
**Line:** 255:255
**Comment:**
*Custom Rule: Add a short docstring to this newly added test method to
describe the invalid hash algorithm scenario and expected behavior.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8b8cade2e11c5f4ab882189d972de97c59036d165197fa43f2a25d0185cab82f&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=8b8cade2e11c5f4ab882189d972de97c59036d165197fa43f2a25d0185cab82f&reaction=dislike'>👎</a>
##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
rv = self.client.put("/api/v1/me/", json={})
assert rv.status_code == 400
+ def test_update_me_rejects_password_when_auth_db(self) -> None:
+ """Reject password changes via PUT /me with a 400 when AUTH_TYPE is
AUTH_DB."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(meUri, json={"password": "ignored"})
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_put_my_password_wrong_current(self) -> None:
+ """Reject the change with a 400 when current_password is incorrect."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": "not-the-admin-password",
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["message"] == "Incorrect current password."
+
+ def test_put_my_password_weak_new(self) -> None:
+ """Verify a new password failing policy is rejected with a 400."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "short",
+ "confirm_password": "short",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "new_password" in data["message"]
+
+ def test_put_my_password_success(self) -> None:
+ """Cover the successful password change flow for the current user."""
+ self.login(ADMIN_USERNAME)
+ new_password = "AnotherStr0ng!Pass" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ rv2 = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": new_password,
+ "new_password": "YetAnotherStr0ng!Pw",
+ "confirm_password": "YetAnotherStr0ng!Pw",
+ },
+ )
+ assert rv2.status_code == 200
+ finally:
+ self._restore_admin_default_password()
+
+ def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+ """
+ Rotating the session stamp on password change logs out other clients
that
+ still present the pre-change signed session cookie.
+ """
+ from flask.testing import FlaskClient
+
+ app = superset_integration_app
+ client_a: FlaskClient = app.test_client()
+ login(client_a, ADMIN_USERNAME)
+
+ session_cookie = client_a.get_cookie("session")
+ assert session_cookie is not None
+
+ client_b: FlaskClient = app.test_client()
+ client_b.set_cookie(
+ key="session",
+ value=session_cookie.value,
+ domain=session_cookie.domain or "localhost",
+ path=session_cookie.path or "/",
+ )
+
+ new_password = "AnotherStr0ng!PassClone" # noqa: S105
+ try:
+ rv = client_a.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ assert client_b.get(meUri).status_code == 401
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_clears_remember_cookie(self) -> None:
+ """
+ Password change schedules Flask-Login remember-me cookie deletion.
+
+ Superset does not expose remember-me in the React login UI; this is
defensive
+ hardening for FAB / Flask-Login persistent cookies.
+ """
+ app = superset_integration_app
+ remember_name = app.config.get("REMEMBER_COOKIE_NAME",
"remember_token")
+ self.login(ADMIN_USERNAME)
+ self.client.set_cookie(remember_name, "stale-remember-token")
+
+ new_password = "AnotherStr0ng!PassRemember" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ set_cookies = rv.headers.getlist("Set-Cookie")
+ cleared = any(
+ remember_name in header
+ and ("=;" in header or "Max-Age=0" in header or "1970" in
header)
+ for header in set_cookies
+ )
+ assert cleared, f"Expected remember cookie clear in {set_cookies}"
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_invalid_hash_algorithm(self) -> None:
+ self.login(ADMIN_USERNAME)
+ original_auth_db_config = superset_integration_app.config.get(
+ "AUTH_DB_CONFIG", {}
+ )
+ try:
+ superset_integration_app.config["AUTH_DB_CONFIG"] = {
+ **original_auth_db_config,
+ "password_hash_algorithm": "invalid",
+ }
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ finally:
+ superset_integration_app.config["AUTH_DB_CONFIG"] =
original_auth_db_config
+
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "password_hash_algorithm" in data["message"]
+
+ def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
+ self.login(ADMIN_USERNAME)
+ original_auth = superset_integration_app.config["AUTH_TYPE"]
+ try:
+ superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ finally:
+ superset_integration_app.config["AUTH_TYPE"] = original_auth
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_get_my_password_policy_success(self) -> None:
Review Comment:
**Suggestion:** Add a concise docstring to this new test method describing
the successful password policy retrieval case. [custom_rule]
**Severity Level:** Minor ⚠️
<details>
<summary><b>Why it matters? 🤔 </b></summary>
The added test method is missing a docstring in the final code.
This matches the custom rule requiring documentation for newly added Python
functions and classes.
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=841ed9bac8b740df81bc93acea2c59ac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=841ed9bac8b740df81bc93acea2c59ac&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** tests/integration_tests/users/api_tests.py
**Line:** 299:299
**Comment:**
*Custom Rule: Add a concise docstring to this new test method
describing the successful password policy retrieval case.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=93257ce9b0e69a44c3d4db83cf12c63bf5d25a8730c4b22e5095ab4833ba808a&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=93257ce9b0e69a44c3d4db83cf12c63bf5d25a8730c4b22e5095ab4833ba808a&reaction=dislike'>👎</a>
##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
rv = self.client.put("/api/v1/me/", json={})
assert rv.status_code == 400
+ def test_update_me_rejects_password_when_auth_db(self) -> None:
+ """Reject password changes via PUT /me with a 400 when AUTH_TYPE is
AUTH_DB."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(meUri, json={"password": "ignored"})
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_put_my_password_wrong_current(self) -> None:
+ """Reject the change with a 400 when current_password is incorrect."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": "not-the-admin-password",
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["message"] == "Incorrect current password."
+
+ def test_put_my_password_weak_new(self) -> None:
+ """Verify a new password failing policy is rejected with a 400."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "short",
+ "confirm_password": "short",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "new_password" in data["message"]
+
+ def test_put_my_password_success(self) -> None:
+ """Cover the successful password change flow for the current user."""
+ self.login(ADMIN_USERNAME)
+ new_password = "AnotherStr0ng!Pass" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ rv2 = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": new_password,
+ "new_password": "YetAnotherStr0ng!Pw",
+ "confirm_password": "YetAnotherStr0ng!Pw",
+ },
+ )
+ assert rv2.status_code == 200
+ finally:
+ self._restore_admin_default_password()
+
+ def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+ """
+ Rotating the session stamp on password change logs out other clients
that
+ still present the pre-change signed session cookie.
+ """
+ from flask.testing import FlaskClient
+
+ app = superset_integration_app
+ client_a: FlaskClient = app.test_client()
+ login(client_a, ADMIN_USERNAME)
+
+ session_cookie = client_a.get_cookie("session")
+ assert session_cookie is not None
+
+ client_b: FlaskClient = app.test_client()
+ client_b.set_cookie(
+ key="session",
+ value=session_cookie.value,
+ domain=session_cookie.domain or "localhost",
+ path=session_cookie.path or "/",
+ )
+
+ new_password = "AnotherStr0ng!PassClone" # noqa: S105
+ try:
+ rv = client_a.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ assert client_b.get(meUri).status_code == 401
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_clears_remember_cookie(self) -> None:
+ """
+ Password change schedules Flask-Login remember-me cookie deletion.
+
+ Superset does not expose remember-me in the React login UI; this is
defensive
+ hardening for FAB / Flask-Login persistent cookies.
+ """
+ app = superset_integration_app
+ remember_name = app.config.get("REMEMBER_COOKIE_NAME",
"remember_token")
+ self.login(ADMIN_USERNAME)
+ self.client.set_cookie(remember_name, "stale-remember-token")
+
+ new_password = "AnotherStr0ng!PassRemember" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ set_cookies = rv.headers.getlist("Set-Cookie")
+ cleared = any(
+ remember_name in header
+ and ("=;" in header or "Max-Age=0" in header or "1970" in
header)
+ for header in set_cookies
+ )
+ assert cleared, f"Expected remember cookie clear in {set_cookies}"
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_invalid_hash_algorithm(self) -> None:
+ self.login(ADMIN_USERNAME)
+ original_auth_db_config = superset_integration_app.config.get(
+ "AUTH_DB_CONFIG", {}
+ )
+ try:
+ superset_integration_app.config["AUTH_DB_CONFIG"] = {
+ **original_auth_db_config,
+ "password_hash_algorithm": "invalid",
+ }
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ finally:
+ superset_integration_app.config["AUTH_DB_CONFIG"] =
original_auth_db_config
+
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "password_hash_algorithm" in data["message"]
+
+ def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
Review Comment:
**Suggestion:** Add a docstring to this newly added test method explaining
that the password endpoint is unavailable when authentication is not AUTH_DB.
[custom_rule]
**Severity Level:** Minor ⚠️
<details>
<summary><b>Why it matters? 🤔 </b></summary>
This method is newly added and does not have a docstring.
Since the rule explicitly requires docstrings for newly added Python
functions, the violation is real.
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=f8768f0ba3ba40ecb1757fc1281701b3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=f8768f0ba3ba40ecb1757fc1281701b3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** tests/integration_tests/users/api_tests.py
**Line:** 280:280
**Comment:**
*Custom Rule: Add a docstring to this newly added test method
explaining that the password endpoint is unavailable when authentication is not
AUTH_DB.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3c274863a02ee168d40018c7811cd36dd3e4faec6f78fce010af07629bdc1d0e&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=3c274863a02ee168d40018c7811cd36dd3e4faec6f78fce010af07629bdc1d0e&reaction=dislike'>👎</a>
##########
tests/integration_tests/users/api_tests.py:
##########
@@ -100,6 +115,221 @@ def test_update_me_empty_payload(self):
rv = self.client.put("/api/v1/me/", json={})
assert rv.status_code == 400
+ def test_update_me_rejects_password_when_auth_db(self) -> None:
+ """Reject password changes via PUT /me with a 400 when AUTH_TYPE is
AUTH_DB."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(meUri, json={"password": "ignored"})
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_put_my_password_wrong_current(self) -> None:
+ """Reject the change with a 400 when current_password is incorrect."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": "not-the-admin-password",
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["message"] == "Incorrect current password."
+
+ def test_put_my_password_weak_new(self) -> None:
+ """Verify a new password failing policy is rejected with a 400."""
+ self.login(ADMIN_USERNAME)
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "short",
+ "confirm_password": "short",
+ },
+ )
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "new_password" in data["message"]
+
+ def test_put_my_password_success(self) -> None:
+ """Cover the successful password change flow for the current user."""
+ self.login(ADMIN_USERNAME)
+ new_password = "AnotherStr0ng!Pass" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ rv2 = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": new_password,
+ "new_password": "YetAnotherStr0ng!Pw",
+ "confirm_password": "YetAnotherStr0ng!Pw",
+ },
+ )
+ assert rv2.status_code == 200
+ finally:
+ self._restore_admin_default_password()
+
+ def test_put_my_password_invalidates_cloned_session_client(self) -> None:
+ """
+ Rotating the session stamp on password change logs out other clients
that
+ still present the pre-change signed session cookie.
+ """
+ from flask.testing import FlaskClient
+
+ app = superset_integration_app
+ client_a: FlaskClient = app.test_client()
+ login(client_a, ADMIN_USERNAME)
+
+ session_cookie = client_a.get_cookie("session")
+ assert session_cookie is not None
+
+ client_b: FlaskClient = app.test_client()
+ client_b.set_cookie(
+ key="session",
+ value=session_cookie.value,
+ domain=session_cookie.domain or "localhost",
+ path=session_cookie.path or "/",
+ )
+
+ new_password = "AnotherStr0ng!PassClone" # noqa: S105
+ try:
+ rv = client_a.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ assert client_b.get(meUri).status_code == 401
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_clears_remember_cookie(self) -> None:
+ """
+ Password change schedules Flask-Login remember-me cookie deletion.
+
+ Superset does not expose remember-me in the React login UI; this is
defensive
+ hardening for FAB / Flask-Login persistent cookies.
+ """
+ app = superset_integration_app
+ remember_name = app.config.get("REMEMBER_COOKIE_NAME",
"remember_token")
+ self.login(ADMIN_USERNAME)
+ self.client.set_cookie(remember_name, "stale-remember-token")
+
+ new_password = "AnotherStr0ng!PassRemember" # noqa: S105
+ try:
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": new_password,
+ "confirm_password": new_password,
+ },
+ )
+ assert rv.status_code == 200
+
+ set_cookies = rv.headers.getlist("Set-Cookie")
+ cleared = any(
+ remember_name in header
+ and ("=;" in header or "Max-Age=0" in header or "1970" in
header)
+ for header in set_cookies
+ )
+ assert cleared, f"Expected remember cookie clear in {set_cookies}"
+ finally:
+ self._restore_admin_default_password(app)
+
+ def test_put_my_password_invalid_hash_algorithm(self) -> None:
+ self.login(ADMIN_USERNAME)
+ original_auth_db_config = superset_integration_app.config.get(
+ "AUTH_DB_CONFIG", {}
+ )
+ try:
+ superset_integration_app.config["AUTH_DB_CONFIG"] = {
+ **original_auth_db_config,
+ "password_hash_algorithm": "invalid",
+ }
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ finally:
+ superset_integration_app.config["AUTH_DB_CONFIG"] =
original_auth_db_config
+
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "password_hash_algorithm" in data["message"]
+
+ def test_put_my_password_unavailable_when_not_auth_db(self) -> None:
+ self.login(ADMIN_USERNAME)
+ original_auth = superset_integration_app.config["AUTH_TYPE"]
+ try:
+ superset_integration_app.config["AUTH_TYPE"] = AUTH_OAUTH
+ rv = self.client.put(
+ mePasswordUri,
+ json={
+ "current_password": DEFAULT_PASSWORD,
+ "new_password": "AnotherStr0ng!Pass",
+ "confirm_password": "AnotherStr0ng!Pass",
+ },
+ )
+ finally:
+ superset_integration_app.config["AUTH_TYPE"] = original_auth
+ assert rv.status_code == 400
+ data = json.loads(rv.data.decode("utf-8"))
+ assert "AUTH_TYPE is AUTH_DB" in data["message"]
+
+ def test_get_my_password_policy_success(self) -> None:
+ self.login(ADMIN_USERNAME)
+ rv = self.client.get("/api/v1/me/password/policy")
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["result"] == get_public_auth_db_password_policy()
+
+ def test_get_my_password_policy_unavailable_when_not_auth_db(self) -> None:
Review Comment:
**Suggestion:** Add a docstring to this newly added test method clarifying
that the password policy endpoint should fail outside AUTH_DB mode.
[custom_rule]
**Severity Level:** Minor ⚠️
<details>
<summary><b>Why it matters? 🤔 </b></summary>
This newly added test method also lacks a docstring.
The suggestion is valid because the rule requires docstrings for new Python
functions.
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=554a3ce866444942823c6daf9c38719c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=554a3ce866444942823c6daf9c38719c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** tests/integration_tests/users/api_tests.py
**Line:** 306:306
**Comment:**
*Custom Rule: Add a docstring to this newly added test method
clarifying that the password policy endpoint should fail outside AUTH_DB mode.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=78eec783e36141c2ffb327e77741c5dfaaf557654578663c731360ff7e28eb1f&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39469&comment_hash=78eec783e36141c2ffb327e77741c5dfaaf557654578663c731360ff7e28eb1f&reaction=dislike'>👎</a>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]