Add DISABLE_LOCAL_AUTH env variable (#962)

Adds a new env var to disable local auth entirely when using OIDC
authentication

Fixes #922 #834
This commit is contained in:
Alex
2026-05-08 22:11:14 +01:00
committed by GitHub
parent 3305ec9e46
commit 9b8402c9a7
12 changed files with 175 additions and 12 deletions
+32
View File
@@ -381,6 +381,16 @@ class TestSecuritySettings:
assert "inactive" in hint.label.lower()
assert "local admin" in hint.label.lower()
def test_oidc_admin_requirement_hint_absent_when_local_auth_is_disabled(self):
"""OIDC mode should not show the local-admin warning when local auth is disabled."""
from shelfmark.config.security import security_settings
with patch("shelfmark.config.env.DISABLE_LOCAL_AUTH", True):
fields = security_settings()
hint = next((f for f in fields if f.key == "oidc_admin_requirement"), None)
assert hint is None
def test_builtin_option_label_is_local(self):
"""Builtin auth option should be labeled Local."""
from shelfmark.config.security import security_settings
@@ -429,6 +439,28 @@ class TestSecurityOnSave:
assert result["error"] is True
assert "local admin" in result["message"].lower()
def test_on_save_allows_oidc_without_local_admin_when_local_auth_is_disabled(
self, tmp_path, monkeypatch
):
from shelfmark.config.security import _on_save_security
_set_config_dir(monkeypatch, tmp_path)
UserDB(str(tmp_path / "users.db")).initialize()
with patch("shelfmark.config.security_handlers.DISABLE_LOCAL_AUTH", True):
result = _on_save_security(
{
"AUTH_METHOD": "oidc",
"OIDC_DISCOVERY_URL": (
"https://auth.example.com/.well-known/openid-configuration"
),
"OIDC_CLIENT_ID": "shelfmark",
"OIDC_CLIENT_SECRET": "secret123",
}
)
assert result["error"] is False
def test_on_save_blocks_oidc_when_client_id_is_missing(self, tmp_path, monkeypatch):
from shelfmark.config.security import _on_save_security
+28
View File
@@ -126,6 +126,21 @@ class TestLoginSemantics:
assert response.status_code == 403
assert response.get_json()["error"] == "Local authentication is disabled"
@pytest.mark.parametrize("auth_mode", ["builtin", "oidc"])
def test_login_rejects_password_auth_when_local_auth_is_disabled(
self, main_module, client, auth_mode
):
with patch.object(main_module, "get_auth_mode", return_value=auth_mode):
with patch.object(main_module, "DISABLE_LOCAL_AUTH", True):
response = client.post(
"/api/auth/login",
json={"username": "alice", "password": "wrong", "remember_me": False},
)
assert response.status_code == 403
assert response.get_json()["error"] == "Local authentication is disabled"
assert main_module.failed_login_attempts == {}
def test_auth_check_none_mode_reports_full_access(self, main_module, client):
with patch.object(main_module, "get_auth_mode", return_value="none"):
response = client.get("/api/auth/check")
@@ -138,6 +153,19 @@ class TestLoginSemantics:
"is_admin": True,
}
@pytest.mark.parametrize("auth_mode", ["builtin", "oidc"])
def test_auth_check_hides_local_auth_when_disabled(self, main_module, client, auth_mode):
with patch.object(main_module, "get_auth_mode", return_value=auth_mode):
with patch.object(main_module, "DISABLE_LOCAL_AUTH", True):
response = client.get("/api/auth/check")
assert response.status_code == 200
body = response.get_json()
assert body["auth_mode"] == auth_mode
assert body["auth_required"] is True
assert body["authenticated"] is False
assert body["hide_local_auth"] is True
def test_auth_check_includes_display_name_for_authenticated_user(
self, main_module, client, temp_user_db, monkeypatch
):
+27
View File
@@ -2,6 +2,8 @@
import sqlite3
import pytest
from shelfmark.core.auth_modes import (
determine_auth_mode,
get_auth_check_admin_status,
@@ -63,6 +65,31 @@ class TestDetermineAuthMode:
}
assert determine_auth_mode(config, cwa_db_path=None, has_local_admin=False) == "none"
@pytest.mark.parametrize(
("auth_mode", "config"),
[
("builtin", {"AUTH_METHOD": "builtin"}),
(
"oidc",
{
"AUTH_METHOD": "oidc",
"OIDC_DISCOVERY_URL": "https://auth.example.com/.well-known/openid-configuration",
"OIDC_CLIENT_ID": "shelfmark",
},
),
],
)
def test_disable_local_auth_keeps_configured_mode_without_admin(self, auth_mode, config):
assert (
determine_auth_mode(
config,
cwa_db_path=None,
has_local_admin=False,
disable_local_auth=True,
)
== auth_mode
)
def test_load_active_auth_mode_reads_env_backed_cwa_setting(self, monkeypatch, tmp_path):
from shelfmark.core.config import config as app_config