Commit dd53d5e0 authored by Sjoerd Simons's avatar Sjoerd Simons
Browse files

Merge branch 'collabora-staging-add-auth-oidc' into 'collabora/staging'

Add support for Open ID Connect (OIDC) identity provider

See merge request !86
parents a351ec86 3b8533a7
Pipeline #42317 waiting for manual action with stages
in 19 minutes and 55 seconds
......@@ -131,3 +131,35 @@ need to be performed (following example covers `GitLab OAuth2 authentication`_):
.. note:: If SMTP is not set up in LAVA, you can get a 500 Internal server
error. Login will still work despite the error.
Using Open ID Connect (OIDC) authentication providers
-----------------------------------------------------
LAVA server can be configured to authenticate using OIDC providers
such as Keycloack or Azure AD. The OIDC library used is
`mozilla-django-oidc <https://github.com/mozilla/mozilla-django-oidc>`_.
The library does not come pre-installed and must be installed through
external means. (for example, with ``pip``)
To enable OIDC authorization set ``AUTH_OIDC`` dictionary in one of the
configuration files.
Example::
---
AUTH_OIDC:
OIDC_RP_CLIENT_ID: "1"
OIDC_RP_CLIENT_SECRET: "bd01adf93cfb"
OIDC_OP_AUTHORIZATION_ENDPOINT: "http://testprovider:8080/openid/authorize"
OIDC_OP_TOKEN_ENDPOINT: "http://testprovider:8080/openid/token"
OIDC_OP_USER_ENDPOINT: "http://testprovider:8080/openid/userinfo"
See `mozilla-django-oidc settings <https://mozilla-django-oidc.readthedocs.io/en/stable/settings.html>`_
for the list of configuration keys.
One extra setting that LAVA provides is ``LAVA_OIDC_ACCOUNT_NAME``
which sets the login message for OIDC login prompt. For example,
it can be set to ``Azure AD account``. By default it is set to
``Open ID Connect account``.
......@@ -31,8 +31,13 @@ COPY docker/collabora/lava-dpkg.cfg /etc/dpkg/dpkg.cfg.d/lava
RUN apt update && \
apt install -y --no-install-recommends \
/*.deb \
wait-for-it && \
wait-for-it \
python3-pip \
python3-requests \
python3-josepy \
python3-cryptography && \
rm /*deb
RUN pip3 install --no-deps mozilla-django-oidc=="2.0.0"
# Logging env
COPY docker/collabora/logging_env.yaml /
......
......@@ -48,6 +48,13 @@ def ldap_available(request):
return {"ldap_available": ldap_enabled, "login_message_ldap": login_message_ldap}
def oidc_context(request):
return {
"oidc_enabled": settings.OIDC_ENABLED,
"oidc_account_name": settings.LAVA_OIDC_ACCOUNT_NAME,
}
def socialaccount(request):
return {
"socialaccount_enabled": settings.AUTH_SOCIALACCOUNT is not None,
......
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
import logging
class OIDCAuthenticationBackendUsernameFromEmail(OIDCAuthenticationBackend):
def create_user(self, claims):
# TODO try multiple options and fallbacks
logger = logging.getLogger("mozilla_django_oidc")
email = claims.get("email")
logger.info(f"Creating new user for e-mail: {email}")
# On Azure AD the username is not part of the claims
username = email.split("@")[0]
return self.UserModel.objects.create_user(username, email=email)
......@@ -28,6 +28,11 @@ class LavaRequireLoginMiddleware:
HOME_PATH: ClassVar[PurePosixPath] = PurePosixPath("/") / settings.MOUNT_POINT
LOGIN_PATH: ClassVar[PurePosixPath] = PurePosixPath(settings.LOGIN_URL)
SCHEDULER_INTERNALS_PATH: ClassVar[PurePosixPath] = (
HOME_PATH / "scheduler/internal/v1"
)
OIDC_PATH: ClassVar[PurePosixPath] = HOME_PATH / "oidc"
def __init__(self, get_response):
self.get_response = get_response
self.require_login = login_required(get_response)
......@@ -40,6 +45,21 @@ class LavaRequireLoginMiddleware:
if path == cls.LOGIN_PATH:
return True
try:
path.relative_to(cls.SCHEDULER_INTERNALS_PATH)
except ValueError:
...
else:
return True
if settings.OIDC_ENABLED:
try:
path.relative_to(cls.OIDC_PATH)
except ValueError:
...
else:
return True
return False
def __call__(self, request):
......
......@@ -114,6 +114,7 @@ TEMPLATES = [
# LAVA context processors
"lava_server.context_processors.lava",
"lava_server.context_processors.ldap_available",
"lava_server.context_processors.oidc_context",
"lava_server.context_processors.socialaccount",
]
},
......@@ -231,6 +232,11 @@ AUTH_LDAP_GROUP_TYPE = None
# Social accounts support
AUTH_SOCIALACCOUNT = None
# OIDC support
AUTH_OIDC = None
OIDC_ENABLED = False
LAVA_OIDC_ACCOUNT_NAME = "Open ID Connect account"
# Gitlab support
AUTH_GITLAB_URL = None
AUTH_GITLAB_SCOPE = ["read_user"]
......@@ -337,6 +343,7 @@ def update(values):
AUTH_LDAP_USER_SEARCH = values.get("AUTH_LDAP_USER_SEARCH")
AUTH_DEBIAN_SSO = values.get("AUTH_DEBIAN_SSO")
AUTH_SOCIALACCOUNT = values.get("AUTH_SOCIALACCOUNT")
AUTH_OIDC = values.get("AUTH_OIDC")
AUTH_GITLAB_URL = values.get("AUTH_GITLAB_URL")
AUTH_GITLAB_SCOPE = values.get("AUTH_GITLAB_SCOPE")
AUTHENTICATION_BACKENDS = values.get("AUTHENTICATION_BACKENDS")
......@@ -412,6 +419,60 @@ def update(values):
)
SOCIALACCOUNT_PROVIDERS = auth_socialaccount
# OIDC authentication
if AUTH_OIDC is not None:
try:
LAVA_OIDC_ACCOUNT_NAME = AUTH_OIDC.pop("LAVA_OIDC_ACCOUNT_NAME")
except KeyError:
...
oidc_keys = {
"OIDC_OP_AUTHORIZATION_ENDPOINT",
"OIDC_OP_TOKEN_ENDPOINT",
"OIDC_OP_USER_ENDPOINT",
"OIDC_RP_CLIENT_ID",
"OIDC_RP_CLIENT_SECRET",
"OIDC_VERIFY_JWT",
"OIDC_VERIFY_KID",
"OIDC_USE_NONCE",
"OIDC_VERIFY_SSL",
"OIDC_TIMEOUT",
"OIDC_PROXY",
"OIDC_EXEMPT_URLS",
"OIDC_CREATE_USER",
"OIDC_STATE_SIZE",
"OIDC_NONCE_SIZE",
"OIDC_MAX_STATES",
"OIDC_REDIRECT_FIELD_NAME",
"OIDC_CALLBACK_CLASS",
"OIDC_AUTHENTICATE_CLASS",
"OIDC_RP_SCOPES",
"OIDC_STORE_ACCESS_TOKEN",
"OIDC_STORE_ID_TOKEN",
"OIDC_AUTH_REQUEST_EXTRA_PARAMS",
"OIDC_RP_SIGN_ALGO",
"OIDC_RP_IDP_SIGN_KEY",
"OIDC_OP_LOGOUT_URL_METHOD",
"OIDC_AUTHENTICATION_CALLBACK_URL",
"OIDC_ALLOW_UNSECURED_JWT",
"OIDC_TOKEN_USE_BASIC_AUTH",
}
if not oidc_keys.issuperset(AUTH_OIDC.keys()):
raise ImproperlyConfigured(
"Unknown OIDC configuration keys: ",
set(AUTH_OIDC.keys()).difference(oidc_keys),
)
INSTALLED_APPS.append("mozilla_django_oidc")
AUTHENTICATION_BACKENDS.append(
"lava_server.oidc_sso.OIDCAuthenticationBackendUsernameFromEmail"
)
MIDDLEWARE.append("mozilla_django_oidc.middleware.SessionRefresh")
OIDC_ENABLED = True
locals().update(AUTH_OIDC)
# LDAP authentication config
if AUTH_LDAP_SERVER_URI:
INSTALLED_APPS.append("ldap")
......@@ -542,6 +603,11 @@ def update(values):
"level": "DEBUG",
"propagate": False,
},
"mozilla_django_oidc": {
"handlers": ["logfile"],
"level": "DEBUG",
"propagate": True,
},
},
}
......
......@@ -52,10 +52,14 @@
</form>
</div>
{% endif %}
{% if oidc_enabled %}
<div class="col-md-4">
<h4>Local account</h4>
<hr/>
<h4 class="modal-header">{{ oidc_account_name }}</h4>
<a href="{% url 'oidc_authentication_init' %}">Login</a>
</div>
{% endif %}
<div class="col-md-4">
<h4 class="modal-header">Local account</h4>
<form class="form-horizontal" method="post" action="{% url 'login' %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.GET.next }}" />
......
......@@ -166,6 +166,14 @@ urlpatterns = [
),
]
if settings.OIDC_ENABLED:
urlpatterns.append(
url(
r"^{mount_point}oidc/".format(mount_point=settings.MOUNT_POINT),
include("mozilla_django_oidc.urls"),
)
)
if settings.USE_DEBUG_TOOLBAR:
import debug_toolbar
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment