Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ops 441 cannot lock yourself #2678

Merged
merged 5 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion backend/ops_api/ops/error_handlers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask_jwt_extended.exceptions import NoAuthorizationError
from marshmallow import ValidationError
from sqlalchemy.exc import PendingRollbackError
from werkzeug.exceptions import Forbidden, NotFound
from werkzeug.exceptions import BadRequest, Forbidden, NotFound

from ops_api.ops.auth.exceptions import AuthenticationError, InvalidUserSessionError, NotActiveUserError
from ops_api.ops.utils.response import make_response_with_headers
Expand Down Expand Up @@ -58,6 +58,11 @@ def handle_exception_forbidden(e):
app.logger.exception(e)
return make_response_with_headers({}, 403)

@app.errorhandler(BadRequest)
def handle_exception_bad_request(e):
app.logger.exception(e)
return make_response_with_headers({}, 400)

@app.errorhandler(Exception)
def handle_exception(e):
app.logger.exception(e)
Expand Down
11 changes: 7 additions & 4 deletions backend/ops_api/ops/services/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from sqlalchemy import ColumnElement, select
from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden, NotFound
from werkzeug.exceptions import BadRequest, Forbidden, NotFound

from models import Role, User, UserStatus
from ops_api.ops.auth.utils import deactivate_all_user_sessions, get_all_user_sessions
Expand Down Expand Up @@ -38,17 +38,20 @@ def update_user(session: Session, **kwargs) -> User:
data = kwargs.get("data", {})

if not data:
raise RuntimeError("No data provided to update user.")
raise BadRequest("No data provided to update user.")

if "id" in data and user_id != data.get("id"):
raise Forbidden("User ID does not match ID in data.")
raise BadRequest("User ID does not match ID in data.")

get_user(session, id=user_id) # Ensure user exists

if not is_user_admin(request_user):
raise Forbidden("You do not have permission to update this user.")
raise BadRequest("You do not have permission to update this user.")

if data.get("status") in [UserStatus.INACTIVE, UserStatus.LOCKED]:
if user_id == request_user.id:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there it is!

raise BadRequest("You cannot deactivate yourself.")

user_sessions = get_all_user_sessions(user_id, session)
deactivate_all_user_sessions(user_sessions)

Expand Down
16 changes: 14 additions & 2 deletions backend/ops_api/tests/ops/users/test_users_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_patch_user_unauthorized_different_user(client, loaded_db, test_non_admi
json={"id": test_user.id, "email": "[email protected]", "first_name": "New First Name"},
headers={"Authorization": f"Bearer {str(access_token)}"},
)
assert response.status_code == 403
assert response.status_code == 400


@pytest.mark.usefixtures("app_ctx")
Expand Down Expand Up @@ -121,7 +121,7 @@ def test_patch_user_must_be_user_admin_to_change_status(client, test_user, test_
json={"id": test_non_admin_user.id, "status": UserStatus.ACTIVE.name},
headers={"Authorization": f"Bearer {str(access_token)}"},
)
assert response.status_code == 403
assert response.status_code == 400


def test_patch_user_changing_status_deactivates_user_session(auth_client, new_user, loaded_db):
Expand Down Expand Up @@ -157,3 +157,15 @@ def test_patch_user_changing_status_deactivates_user_session(auth_client, new_us
# cleanup
loaded_db.delete(user_session)
loaded_db.commit()


@pytest.mark.usefixtures("app_ctx")
def test_patch_user_cannot_deactivate_yourself(auth_client, new_user, loaded_db, test_admin_user):
response = auth_client.patch(
url_for("api.users-item", id=test_admin_user.id),
json={
"id": test_admin_user.id,
"status": UserStatus.INACTIVE.name,
},
)
assert response.status_code == 400
15 changes: 12 additions & 3 deletions backend/ops_api/tests/ops/users/test_users_put.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_put_user_unauthorized_different_user(client, loaded_db, test_non_admin_
json={"id": test_user.id, "email": "[email protected]", "first_name": "New First Name"},
headers={"Authorization": f"Bearer {str(access_token)}"},
)
assert response.status_code == 403
assert response.status_code == 400


@pytest.mark.usefixtures("app_ctx")
Expand Down Expand Up @@ -168,7 +168,7 @@ def test_put_user_wrong_user(auth_client, new_user, loaded_db, test_admin_user):
url_for("api.users-item", id=new_user.id),
json={"id": 0, "email": "[email protected]"},
)
assert response.status_code == 403
assert response.status_code == 400


def test_put_user_must_be_user_admin_to_change_status(client, test_user, test_non_admin_user):
Expand All @@ -181,7 +181,7 @@ def test_put_user_must_be_user_admin_to_change_status(client, test_user, test_no
json={"id": test_non_admin_user.id, "status": UserStatus.ACTIVE.name},
headers={"Authorization": f"Bearer {str(access_token)}"},
)
assert response.status_code == 403
assert response.status_code == 400


def test_put_user_changing_status_deactivates_user_session(auth_client, new_user, loaded_db):
Expand Down Expand Up @@ -222,3 +222,12 @@ def test_put_user_changing_status_deactivates_user_session(auth_client, new_user
# cleanup
loaded_db.delete(user_session)
loaded_db.commit()


@pytest.mark.usefixtures("app_ctx")
def test_put_user__cannot_deactivate_yourself(auth_client, new_user, loaded_db, test_admin_user):
response = auth_client.put(
url_for("api.users-item", id=test_admin_user.id),
json={"email": "[email protected]", "status": UserStatus.INACTIVE.name},
)
assert response.status_code == 400