tweaks
This commit is contained in:
@@ -7,6 +7,7 @@ from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.api.access import router as access_router
|
||||
from app.core.access import (
|
||||
INTERNAL_USER_SUBJECT,
|
||||
get_user_permissions,
|
||||
@@ -15,7 +16,8 @@ from app.core.access import (
|
||||
require_permission,
|
||||
user_has_permission,
|
||||
)
|
||||
from app.core.security import issue_token
|
||||
from app.core.config import settings
|
||||
from app.core.security import issue_token, verify_password
|
||||
from app.db.session import Base, get_db
|
||||
from app.models.access import Permission, Role, User
|
||||
from app.seed_access import PERMISSION_DEFINITIONS, ROLE_DEFINITIONS, SEED_USERS, seed_access
|
||||
@@ -42,6 +44,10 @@ def test_seed_creates_roles_permissions_and_users():
|
||||
assert {role.name for role in db.query(Role).all()} == set(ROLE_DEFINITIONS.keys())
|
||||
assert {p.key for p in db.query(Permission).all()} == {key for key, _ in PERMISSION_DEFINITIONS}
|
||||
assert {user.email for user in db.query(User).all()} == {entry["email"] for entry in SEED_USERS}
|
||||
for user in db.query(User).all():
|
||||
assert user.password_hash is not None
|
||||
assert user.password_hash != settings.admin_password
|
||||
assert verify_password(settings.admin_password, user.password_hash)
|
||||
|
||||
|
||||
def test_seed_is_idempotent():
|
||||
@@ -71,14 +77,20 @@ def test_admin_role_permissions_match_spec():
|
||||
assert "edit_mixes" not in granted
|
||||
|
||||
|
||||
def test_operations_role_is_mix_calculator_only():
|
||||
def test_operations_role_is_mix_calculator_and_throughput_only():
|
||||
db = _build_session()
|
||||
seed_access(db)
|
||||
|
||||
ops = db.query(User).filter_by(email="ops@hunterstockfeeds.com").one()
|
||||
granted = get_user_permissions(ops)
|
||||
|
||||
assert granted == {"view_mix_calculator", "use_mix_calculator", "save_mix_calculator_session"}
|
||||
assert granted == {
|
||||
"view_mix_calculator",
|
||||
"use_mix_calculator",
|
||||
"save_mix_calculator_session",
|
||||
"view_throughput",
|
||||
"edit_throughput",
|
||||
}
|
||||
assert not user_has_permission(ops, "edit_raw_materials")
|
||||
assert not user_has_permission(ops, "view_dashboard")
|
||||
assert not user_has_permission(ops, "manage_users")
|
||||
@@ -158,6 +170,22 @@ def _token_for(user: User) -> str:
|
||||
return issue_token({"sub": INTERNAL_USER_SUBJECT, "user_id": user.id, "email": user.email})
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def access_app_and_db():
|
||||
db = _build_session()
|
||||
seed_access(db)
|
||||
db.commit()
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def override_get_db():
|
||||
yield db
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
app.include_router(access_router)
|
||||
return TestClient(app), db
|
||||
|
||||
|
||||
def test_route_allows_user_with_permission(app_and_db):
|
||||
client, db = app_and_db
|
||||
craig = db.query(User).filter_by(email="craig@hunterstockfeeds.com").one()
|
||||
@@ -234,3 +262,55 @@ def test_require_all_permissions(app_and_db):
|
||||
|
||||
denied = client.get("/needs-all", headers={"Authorization": f"Bearer {_token_for(ops)}"})
|
||||
assert denied.status_code == 403
|
||||
|
||||
|
||||
def test_internal_login_uses_user_password_hash(access_app_and_db):
|
||||
client, db = access_app_and_db
|
||||
admin = db.query(User).filter_by(email="admin@hunterstockfeeds.com").one()
|
||||
|
||||
admin.password_hash = issue_token({"not": "a password"})
|
||||
db.commit()
|
||||
|
||||
denied = client.post(
|
||||
"/api/access/login",
|
||||
json={"email": admin.email, "password": settings.admin_password},
|
||||
)
|
||||
assert denied.status_code == 401
|
||||
|
||||
|
||||
def test_internal_user_can_change_own_password(access_app_and_db):
|
||||
client, db = access_app_and_db
|
||||
admin = db.query(User).filter_by(email="admin@hunterstockfeeds.com").one()
|
||||
|
||||
login_response = client.post(
|
||||
"/api/access/login",
|
||||
json={"email": admin.email, "password": settings.admin_password},
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
update_response = client.patch(
|
||||
"/api/access/me",
|
||||
json={
|
||||
"current_password": settings.admin_password,
|
||||
"new_password": "new-personal-password",
|
||||
},
|
||||
cookies=login_response.cookies,
|
||||
)
|
||||
assert update_response.status_code == 200
|
||||
|
||||
db.refresh(admin)
|
||||
assert admin.password_hash is not None
|
||||
assert verify_password("new-personal-password", admin.password_hash)
|
||||
assert not verify_password(settings.admin_password, admin.password_hash)
|
||||
|
||||
old_login = client.post(
|
||||
"/api/access/login",
|
||||
json={"email": admin.email, "password": settings.admin_password},
|
||||
)
|
||||
assert old_login.status_code == 401
|
||||
|
||||
new_login = client.post(
|
||||
"/api/access/login",
|
||||
json={"email": admin.email, "password": "new-personal-password"},
|
||||
)
|
||||
assert new_login.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user