test(auth): add positive controls for local login and tighten basic auth check

- Add flag-off login page and admin password tests so the refusal tests can't pass for unrelated reasons
- Expect exactly 401 from refused basic auth
- Gate OIDC_DISABLE_LOCAL_LOGIN on an explicit OIDC_CONFIGURED flag
- Realign the README OIDC table, use "email" consistently, and warn about lockout with OIDC_ALLOW_SIGNUP=false
This commit is contained in:
Herculino Trotta committed 2026-11-02 16:41:52 -03:00
1 parent 161fc19721
commit 5b98779070
4 files changed
+33 -15

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ TASK_WORKERS=1 # This only work if you're using the single container option. Inc
# Trust all provider emails, including false or missing email_verified claims.
# Set to false to require provider-verified emails before linking existing accounts.
#OIDC_TRUST_EMAIL=true
# Only allow signing in through OIDC: e-mail and password logins are refused everywhere.
# Only allow signing in through OIDC: email and password logins are refused everywhere.
#OIDC_DISABLE_LOCAL_LOGIN=false
# Personal access tokens. How often (seconds) a token's last_used_at is rewritten.
+9 -9
View File
@@ -168,15 +168,15 @@ WYGIWYH supports login via OpenID Connect (OIDC) through `django-allauth`. This
To configure OIDC, you need to set the following environment variables:
| Variable | Description |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `OIDC_CLIENT_NAME` | The name of the provider. will be displayed in the login page. Defaults to `OpenID Connect` |
| `OIDC_CLIENT_ID` | The Client ID provided by your OIDC provider. |
| `OIDC_CLIENT_SECRET` | The Client Secret provided by your OIDC provider. |
| `OIDC_SERVER_URL` | The base URL of your OIDC provider's discovery document or authorization server (e.g., `https://your-provider.com/auth/realms/your-realm`). `django-allauth` will use this to discover the necessary endpoints (authorization, token, userinfo, etc.). |
| `OIDC_ALLOW_SIGNUP` | Allow the automatic creation of inexistent accounts on a successfull authentication. Defaults to `true`. |
| `OIDC_TRUST_EMAIL` | Treat every email sent by your provider as verified, linking it to a matching existing local account even without the `email_verified` claim. Set to `false` to only link verified emails. Defaults to `true`. |
| `OIDC_DISABLE_LOCAL_LOGIN` | Only allow signing in through your OIDC provider. E-mail and password logins are refused everywhere: the login page, the admin and the API's basic authentication. API tokens and OAuth apps keep working. Only takes effect when OIDC is configured, and changing it signs everyone out once. Defaults to `false`. |
| Variable | Description |
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `OIDC_CLIENT_NAME` | The name of the provider. will be displayed in the login page. Defaults to `OpenID Connect` |
| `OIDC_CLIENT_ID` | The Client ID provided by your OIDC provider. |
| `OIDC_CLIENT_SECRET` | The Client Secret provided by your OIDC provider. |
| `OIDC_SERVER_URL` | The base URL of your OIDC provider's discovery document or authorization server (e.g., `https://your-provider.com/auth/realms/your-realm`). `django-allauth` will use this to discover the necessary endpoints (authorization, token, userinfo, etc.). |
| `OIDC_ALLOW_SIGNUP` | Allow the automatic creation of inexistent accounts on a successfull authentication. Defaults to `true`. |
| `OIDC_TRUST_EMAIL` | Treat every email sent by your provider as verified, linking it to a matching existing local account even without the `email_verified` claim. Set to `false` to only link verified emails. Defaults to `true`. |
| `OIDC_DISABLE_LOCAL_LOGIN` | Only allow signing in through your OIDC provider. Email and password logins are refused everywhere: the login page, the admin and the API's basic authentication. API tokens and OAuth apps keep working. Only takes effect when OIDC is configured, and changing it signs everyone out once. If `OIDC_ALLOW_SIGNUP` is `false`, make sure an admin's local account email matches their provider account first, or nobody will be able to sign in. Defaults to `false`. |
**Callback URL (Redirect URI):**
+4 -3
View File
@@ -359,11 +359,12 @@ AUTHENTICATION_BACKENDS = [
SOCIALACCOUNT_PROVIDERS = {"openid_connect": {"APPS": []}}
if (
OIDC_CONFIGURED = bool(
os.getenv("OIDC_CLIENT_ID")
and os.getenv("OIDC_CLIENT_SECRET")
and os.getenv("OIDC_SERVER_URL")
):
)
if OIDC_CONFIGURED:
SOCIALACCOUNT_PROVIDERS["openid_connect"]["APPS"].append(
{
"provider_id": slugify(os.getenv("OIDC_CLIENT_NAME", "OpenID Connect")),
@@ -396,7 +397,7 @@ SOCIALACCOUNT_ADAPTER = "apps.users.adapters.AutoConnectSocialAccountAdapter"
# Only takes effect when an OIDC provider is configured, so an instance can't
# be left without any way to sign in.
OIDC_DISABLE_LOCAL_LOGIN = (
bool(SOCIALACCOUNT_PROVIDERS["openid_connect"]["APPS"])
OIDC_CONFIGURED
and os.getenv("OIDC_DISABLE_LOCAL_LOGIN", "false").lower() == "true"
)
if OIDC_DISABLE_LOCAL_LOGIN:
+19 -2
View File
@@ -200,7 +200,8 @@ def _basic_auth(email, password):
@override_settings(STORAGES=PLAIN_STATIC_FILES, WHITENOISE_AUTOREFRESH=True)
class LocalLoginEnabledByDefaultTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(
# Superuser so the admin login is allowed; mirrors LocalLoginDisabledTests.
self.user = get_user_model().objects.create_superuser(
email="user@example.com", password="test-password"
)
@@ -215,6 +216,22 @@ class LocalLoginEnabledByDefaultTests(TestCase):
self.assertContains(response, 'name="password"')
def test_login_page_accepts_a_password(self):
self.client.post(
reverse("login"),
{"username": "user@example.com", "password": "test-password"},
)
self.assertEqual(self.client.session.get(SESSION_KEY), str(self.user.pk))
def test_admin_accepts_a_password(self):
self.client.post(
reverse("admin:login"),
{"username": "user@example.com", "password": "test-password"},
)
self.assertEqual(self.client.session.get(SESSION_KEY), str(self.user.pk))
def test_api_basic_auth_works(self):
response = self.client.get(
"/api/accounts/",
@@ -277,7 +294,7 @@ class LocalLoginDisabledTests(TestCase):
HTTP_AUTHORIZATION=_basic_auth("user@example.com", "test-password"),
)
self.assertIn(response.status_code, (401, 403))
self.assertEqual(response.status_code, 401)
def test_oidc_login_still_works(self):
request = _complete_oidc_login(