```
More details about configuration option for various providers can be found in the [allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html#provider-specifics).
+
+### Disabling Regular Login
+
+Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting.
Defaults to 'https'
+#### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN}
+
+: Disables the regular frontend username / password login, i.e. once you have setup SSO. Note that the Django admin login cannot be disabled.
+
+ Defaults to False
+
## OCR settings {#ocr}
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
return {
"EMAIL_ENABLED": django_settings.EMAIL_HOST != "localhost"
or django_settings.EMAIL_HOST_USER != "",
+ "DISABLE_REGULAR_LOGIN": django_settings.DISABLE_REGULAR_LOGIN,
}
{% translate "Share link has expired." %}
</div>
{% endif %}
- {% translate "Username" as i18n_username %}
- {% translate "Password" as i18n_password %}
- <div class="form-floating">
- <input type="text" name="login" id="inputUsername" placeholder="{{ i18n_username }}" class="form-control" autocorrect="off" autocapitalize="none" required autofocus>
- <label for="inputUsername">{{ i18n_username }}</label>
- </div>
- <div class="form-floating">
- <input type="password" name="password" id="inputPassword" placeholder="{{ i18n_password }}" class="form-control" required>
- <label for="inputPassword">{{ i18n_password }}</label>
- </div>
- <div class="d-grid mt-3">
- <button class="btn btn-lg btn-primary" type="submit">{% translate "Sign in" %}</button>
- </div>
- {% if EMAIL_ENABLED %}
- <div class="d-grid mt-3">
- <a class="btn btn-link" href="{% url 'account_reset_password' %}">{% translate "Forgot your password?" %}</a>
- </div>
+ {% if not DISABLE_REGULAR_LOGIN %}
+ {% translate "Username" as i18n_username %}
+ {% translate "Password" as i18n_password %}
+ <div class="form-floating">
+ <input type="text" name="login" id="inputUsername" placeholder="{{ i18n_username }}" class="form-control" autocorrect="off" autocapitalize="none" required autofocus>
+ <label for="inputUsername">{{ i18n_username }}</label>
+ </div>
+ <div class="form-floating">
+ <input type="password" name="password" id="inputPassword" placeholder="{{ i18n_password }}" class="form-control" required>
+ <label for="inputPassword">{{ i18n_password }}</label>
+ </div>
+ <div class="d-grid mt-3">
+ <button class="btn btn-lg btn-primary" type="submit">{% translate "Sign in" %}</button>
+ </div>
+ {% if EMAIL_ENABLED %}
+ <div class="d-grid mt-3">
+ <a class="btn btn-link" href="{% url 'account_reset_password' %}">{% translate "Forgot your password?" %}</a>
+ </div>
+ {% endif %}
{% endif %}
</form>
{% load allauth socialaccount %}
{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
- <p class="mt-3">{% translate "or sign in via" %}</p>
+ {% if not DISABLE_REGULAR_LOGIN %}
+ <p class="mt-3">{% translate "or sign in via" %}</p>
+ {% endif %}
<ul class="m-0 p-0">
{% for provider in socialaccount_providers %}
{% if provider.id == "openid" %}
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-02-14 16:47-0800\n"
+"POT-Creation-Date: 2024-02-18 22:27-0800\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
msgid "Share link has expired."
msgstr ""
-#: documents/templates/account/login.html:61
+#: documents/templates/account/login.html:62
#: documents/templates/socialaccount/signup.html:56
msgid "Username"
msgstr ""
-#: documents/templates/account/login.html:62
+#: documents/templates/account/login.html:63
msgid "Password"
msgstr ""
-#: documents/templates/account/login.html:72
+#: documents/templates/account/login.html:73
msgid "Sign in"
msgstr ""
-#: documents/templates/account/login.html:76
+#: documents/templates/account/login.html:77
msgid "Forgot your password?"
msgstr ""
-#: documents/templates/account/login.html:83
+#: documents/templates/account/login.html:86
msgid "or sign in via"
msgstr ""
msgid "paperless application settings"
msgstr ""
-#: paperless/settings.py:642
+#: paperless/settings.py:644
msgid "English (US)"
msgstr ""
-#: paperless/settings.py:643
+#: paperless/settings.py:645
msgid "Arabic"
msgstr ""
-#: paperless/settings.py:644
+#: paperless/settings.py:646
msgid "Afrikaans"
msgstr ""
-#: paperless/settings.py:645
+#: paperless/settings.py:647
msgid "Belarusian"
msgstr ""
-#: paperless/settings.py:646
+#: paperless/settings.py:648
msgid "Bulgarian"
msgstr ""
-#: paperless/settings.py:647
+#: paperless/settings.py:649
msgid "Catalan"
msgstr ""
-#: paperless/settings.py:648
+#: paperless/settings.py:650
msgid "Czech"
msgstr ""
-#: paperless/settings.py:649
+#: paperless/settings.py:651
msgid "Danish"
msgstr ""
-#: paperless/settings.py:650
+#: paperless/settings.py:652
msgid "German"
msgstr ""
-#: paperless/settings.py:651
+#: paperless/settings.py:653
msgid "Greek"
msgstr ""
-#: paperless/settings.py:652
+#: paperless/settings.py:654
msgid "English (GB)"
msgstr ""
-#: paperless/settings.py:653
+#: paperless/settings.py:655
msgid "Spanish"
msgstr ""
-#: paperless/settings.py:654
+#: paperless/settings.py:656
msgid "Finnish"
msgstr ""
-#: paperless/settings.py:655
+#: paperless/settings.py:657
msgid "French"
msgstr ""
-#: paperless/settings.py:656
+#: paperless/settings.py:658
msgid "Hungarian"
msgstr ""
-#: paperless/settings.py:657
+#: paperless/settings.py:659
msgid "Italian"
msgstr ""
-#: paperless/settings.py:658
+#: paperless/settings.py:660
msgid "Japanese"
msgstr ""
-#: paperless/settings.py:659
+#: paperless/settings.py:661
msgid "Luxembourgish"
msgstr ""
-#: paperless/settings.py:660
+#: paperless/settings.py:662
msgid "Norwegian"
msgstr ""
-#: paperless/settings.py:661
+#: paperless/settings.py:663
msgid "Dutch"
msgstr ""
-#: paperless/settings.py:662
+#: paperless/settings.py:664
msgid "Polish"
msgstr ""
-#: paperless/settings.py:663
+#: paperless/settings.py:665
msgid "Portuguese (Brazil)"
msgstr ""
-#: paperless/settings.py:664
+#: paperless/settings.py:666
msgid "Portuguese"
msgstr ""
-#: paperless/settings.py:665
+#: paperless/settings.py:667
msgid "Romanian"
msgstr ""
-#: paperless/settings.py:666
+#: paperless/settings.py:668
msgid "Russian"
msgstr ""
-#: paperless/settings.py:667
+#: paperless/settings.py:669
msgid "Slovak"
msgstr ""
-#: paperless/settings.py:668
+#: paperless/settings.py:670
msgid "Slovenian"
msgstr ""
-#: paperless/settings.py:669
+#: paperless/settings.py:671
msgid "Serbian"
msgstr ""
-#: paperless/settings.py:670
+#: paperless/settings.py:672
msgid "Swedish"
msgstr ""
-#: paperless/settings.py:671
+#: paperless/settings.py:673
msgid "Turkish"
msgstr ""
-#: paperless/settings.py:672
+#: paperless/settings.py:674
msgid "Ukrainian"
msgstr ""
-#: paperless/settings.py:673
+#: paperless/settings.py:675
msgid "Chinese Simplified"
msgstr ""
from allauth.core import context
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.conf import settings
+from django.forms import ValidationError
from django.urls import reverse
class CustomAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
+ """
+ Check whether the site is open for signups, which can be
+ disabled via the ACCOUNT_ALLOW_SIGNUPS setting.
+ """
allow_signups = super().is_open_for_signup(request)
# Override with setting, otherwise default to super.
return getattr(settings, "ACCOUNT_ALLOW_SIGNUPS", allow_signups)
+ def pre_authenticate(self, request, **credentials):
+ """
+ Called prior to calling the authenticate method on the
+ authentication backend. If login is disabled using DISABLE_REGULAR_LOGIN,
+ raise ValidationError to prevent the login.
+ """
+ if settings.DISABLE_REGULAR_LOGIN:
+ raise ValidationError("Regular login is disabled")
+
+ return super().pre_authenticate(request, **credentials)
+
def is_safe_url(self, url):
- # see https://github.com/paperless-ngx/paperless-ngx/issues/5780
+ """
+ Check if the URL is a safe URL.
+ See https://github.com/paperless-ngx/paperless-ngx/issues/5780
+ """
from django.utils.http import url_has_allowed_host_and_scheme
# get_host already validates the given host, so no need to check it again
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin):
+ """
+ Check whether the site is open for signups via social account, which can be
+ disabled via the SOCIALACCOUNT_ALLOW_SIGNUPS setting.
+ """
allow_signups = super().is_open_for_signup(request, sociallogin)
# Override with setting, otherwise default to super.
return getattr(settings, "SOCIALACCOUNT_ALLOW_SIGNUPS", allow_signups)
return url
def populate_user(self, request, sociallogin, data):
+ """
+ Populate the user with data from the social account. Stub is kept in case
+ global default permissions are implemented in the future.
+ """
# TODO: If default global permissions are implemented, should also be here
return super().populate_user(request, sociallogin, data) # pragma: no cover
os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
)
+DISABLE_REGULAR_LOGIN = __get_boolean("PAPERLESS_DISABLE_REGULAR_LOGIN")
+
AUTO_LOGIN_USERNAME = os.getenv("PAPERLESS_AUTO_LOGIN_USERNAME")
if AUTO_LOGIN_USERNAME:
from allauth.core import context
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
from django.conf import settings
+from django.forms import ValidationError
from django.http import HttpRequest
from django.test import TestCase
from django.test import override_settings
# False because request host is not in allowed hosts
self.assertFalse(adapter.is_safe_url(url))
+ @mock.patch("allauth.core.ratelimit._consume_rate", return_value=True)
+ def test_pre_authenticate(self, mock_consume_rate):
+ adapter = get_adapter()
+ request = HttpRequest()
+ request.get_host = mock.Mock(return_value="example.com")
+
+ settings.DISABLE_REGULAR_LOGIN = False
+ adapter.pre_authenticate(request)
+
+ settings.DISABLE_REGULAR_LOGIN = True
+ with self.assertRaises(ValidationError):
+ adapter.pre_authenticate(request)
+
class TestCustomSocialAccountAdapter(TestCase):
def test_is_open_for_signup(self):