Closes: #15842 - Option to hide local login form if SSO is in use (#18924)

Closes: #15842

Branched from #18145 by @tobiasge

Provides a new LOGIN_FORM_HIDDEN setting which allows the administrator to hide the local login form, intended only to be used when SSO is used exclusively for authentication. Note that this means local login will be impossible in the event of SSO provider issues, and can be remedied only through a change to the application config and a restart of the service.


* #15842 - Hide login form

This doesn't implement the full solution proposed in #15842 but enables
administrators to hide the login form when users should only login with a SSO
provider. To prevent a complete lockout when the SSO provider is having
issues the GET parameter `skipsso` can be added to the login URL to show
the form regardless.

* Remove skipsso backdoor

* Add warning

---------

Co-authored-by: Tobias Genannt <tobias.genannt@qbeyond.de>
This commit is contained in:
bctiemann 2025-03-17 13:02:18 -04:00 committed by GitHub
parent 1b4e00aeda
commit f69de12c6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 57 additions and 33 deletions

View File

@ -186,6 +186,17 @@ The lifetime (in seconds) of the authentication cookie issued to a NetBox user u
--- ---
## LOGIN_FORM_HIDDEN
Default: False
Option to hide the login form when only SSO authentication is in use.
!!! warning
If the SSO provider is unreachable, login to NetBox will be impossible if this option is enabled. The only recourse is to disable it in the local configuration and restart the NetBox service.
---
## LOGOUT_REDIRECT_URL ## LOGOUT_REDIRECT_URL
Default: `'home'` Default: `'home'`

View File

@ -89,10 +89,12 @@ class LoginView(View):
if request.user.is_authenticated: if request.user.is_authenticated:
logger = logging.getLogger('netbox.auth.login') logger = logging.getLogger('netbox.auth.login')
return self.redirect_to_next(request, logger) return self.redirect_to_next(request, logger)
login_form_hidden = settings.LOGIN_FORM_HIDDEN
return render(request, self.template_name, { return render(request, self.template_name, {
'form': form, 'form': form,
'auth_backends': self.get_auth_backends(request), 'auth_backends': self.get_auth_backends(request),
'login_form_hidden': login_form_hidden,
}) })
def post(self, request): def post(self, request):

View File

@ -164,6 +164,9 @@ LOGIN_REQUIRED = True
# re-authenticate. (Default: 1209600 [14 days]) # re-authenticate. (Default: 1209600 [14 days])
LOGIN_TIMEOUT = None LOGIN_TIMEOUT = None
# Hide the login form. Useful when only allowing SSO authentication.
LOGIN_FORM_HIDDEN = False
# The view name or URL to which users are redirected after logging out. # The view name or URL to which users are redirected after logging out.
LOGOUT_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'home'

View File

@ -129,6 +129,7 @@ LOGGING = getattr(configuration, 'LOGGING', {})
LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False) LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False)
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', True) LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', True)
LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None) LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
LOGIN_FORM_HIDDEN = getattr(configuration, 'LOGIN_FORM_HIDDEN', False)
LOGOUT_REDIRECT_URL = getattr(configuration, 'LOGOUT_REDIRECT_URL', 'home') LOGOUT_REDIRECT_URL = getattr(configuration, 'LOGOUT_REDIRECT_URL', 'home')
MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/') MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/')
METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False) METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)

View File

@ -34,48 +34,55 @@
{% endif %} {% endif %}
<div class="card card-md"> <div class="card card-md">
<div class="card-body"> {% if not login_form_hidden %}
<h2 class="text-center mb-4">{% trans "Log In" %}</h2> <div class="card-body">
<h2 class="text-center mb-4">{% trans "Log In" %}</h2>
{# Login form #} {# Login form #}
<form action="{% url 'login' %}" method="post"> <form action="{% url 'login' %}" method="post">
{% csrf_token %} {% csrf_token %}
{# Set post-login URL #} {# Set post-login URL #}
{% if 'next' in request.GET %} {% if 'next' in request.GET %}
<input type="hidden" name="next" value="{{ request.GET.next }}" /> <input type="hidden" name="next" value="{{ request.GET.next }}" />
{% elif 'next' in request.POST %} {% elif 'next' in request.POST %}
<input type="hidden" name="next" value="{{ request.POST.next }}" /> <input type="hidden" name="next" value="{{ request.POST.next }}" />
{% endif %} {% endif %}
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="id_username" class="form-label">{{ form.username.label }}</label> <label for="id_username" class="form-label">{{ form.username.label }}</label>
{{ form.username }} {{ form.username }}
{% for error in form.username.errors %} {% for error in form.username.errors %}
<div class="alert alert-danger">{{ error }}</div> <div class="alert alert-danger">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="id_password" class="form-label">{{ form.password.label }}</label> <label for="id_password" class="form-label">{{ form.password.label }}</label>
{{ form.password }} {{ form.password }}
{% for error in form.password.errors %} {% for error in form.password.errors %}
<div class="alert alert-danger">{{ error }}</div> <div class="alert alert-danger">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="form-footer"> <div class="form-footer">
<button type="submit" class="btn btn-primary w-100"> <button type="submit" class="btn btn-primary w-100">
{% trans "Sign In" %} {% trans "Sign In" %}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
{% endif %}
{# SSO login #} {# SSO login #}
{% if auth_backends %} {% if auth_backends %}
<div class="hr-text">{% trans "Or" context "Denotes an alternative option" %}</div> {% if not login_form_hidden %}
<div class="hr-text">{% trans "Or" context "Denotes an alternative option" %}</div>
{% endif %}
<div class="card-body"> <div class="card-body">
{% if login_form_hidden %}
<h2 class="text-center mb-4">{% trans "Log In" %}</h2>
{% endif %}
<div class="row"> <div class="row">
{% for backend in auth_backends %} {% for backend in auth_backends %}
<div class="col"> <div class="col">