mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-08 03:08:29 +02:00
Allow customizing username when creating user through OIDC (#971)
* add ability to cutomize claim user for username generation on oidc login * update documentation with new OIDC options * oidc: also normalize custom claim as username * improve tests * improve docs * some more cleanup --------- Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@gmail.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import os
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import URLResolver
|
||||
|
||||
from bookmarks import utils
|
||||
|
||||
|
||||
class OidcSupportTest(TestCase):
|
||||
def test_should_not_add_oidc_urls_by_default(self):
|
||||
@@ -55,9 +57,83 @@ class OidcSupportTest(TestCase):
|
||||
base_settings = importlib.import_module("siteroot.settings.base")
|
||||
importlib.reload(base_settings)
|
||||
|
||||
self.assertEqual(
|
||||
True,
|
||||
base_settings.OIDC_VERIFY_SSL,
|
||||
)
|
||||
self.assertEqual(True, base_settings.OIDC_VERIFY_SSL)
|
||||
self.assertEqual("openid email profile", base_settings.OIDC_RP_SCOPES)
|
||||
self.assertEqual("email", base_settings.OIDC_USERNAME_CLAIM)
|
||||
|
||||
del os.environ["LD_ENABLE_OIDC"]
|
||||
del os.environ["LD_ENABLE_OIDC"] # Remove the temporary environment variable
|
||||
|
||||
@override_settings(LD_ENABLE_OIDC=True, OIDC_USERNAME_CLAIM="email")
|
||||
def test_username_should_use_email_by_default(self):
|
||||
claims = {
|
||||
"email": "test@example.com",
|
||||
"name": "test name",
|
||||
"given_name": "test given name",
|
||||
"preferred_username": "test preferred username",
|
||||
"nickname": "test nickname",
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
username = utils.generate_username(claims["email"], claims)
|
||||
|
||||
self.assertEqual(claims["email"], username)
|
||||
|
||||
@override_settings(LD_ENABLE_OIDC=True, OIDC_USERNAME_CLAIM="preferred_username")
|
||||
def test_username_should_use_custom_claim(self):
|
||||
claims = {
|
||||
"email": "test@example.com",
|
||||
"name": "test name",
|
||||
"given_name": "test given name",
|
||||
"preferred_username": "test preferred username",
|
||||
"nickname": "test nickname",
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
username = utils.generate_username(claims["email"], claims)
|
||||
|
||||
self.assertEqual(claims["preferred_username"], username)
|
||||
|
||||
@override_settings(LD_ENABLE_OIDC=True, OIDC_USERNAME_CLAIM="nonexistant_claim")
|
||||
def test_username_should_fallback_to_email_for_non_existing_claim(self):
|
||||
claims = {
|
||||
"email": "test@example.com",
|
||||
"name": "test name",
|
||||
"given_name": "test given name",
|
||||
"preferred_username": "test preferred username",
|
||||
"nickname": "test nickname",
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
username = utils.generate_username(claims["email"], claims)
|
||||
|
||||
self.assertEqual(claims["email"], username)
|
||||
|
||||
@override_settings(LD_ENABLE_OIDC=True, OIDC_USERNAME_CLAIM="preferred_username")
|
||||
def test_username_should_fallback_to_email_for_empty_claim(self):
|
||||
claims = {
|
||||
"email": "test@example.com",
|
||||
"name": "test name",
|
||||
"given_name": "test given name",
|
||||
"preferred_username": "",
|
||||
"nickname": "test nickname",
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
username = utils.generate_username(claims["email"], claims)
|
||||
|
||||
self.assertEqual(claims["email"], username)
|
||||
|
||||
@override_settings(LD_ENABLE_OIDC=True, OIDC_USERNAME_CLAIM="preferred_username")
|
||||
def test_username_should_be_normalized(self):
|
||||
claims = {
|
||||
"email": "test@example.com",
|
||||
"name": "test name",
|
||||
"given_name": "test given name",
|
||||
"preferred_username": "NormalizedUser",
|
||||
"nickname": "test nickname",
|
||||
"groups": [],
|
||||
}
|
||||
|
||||
username = utils.generate_username(claims["email"], claims)
|
||||
|
||||
self.assertEqual("NormalizedUser", username)
|
||||
|
@@ -9,6 +9,7 @@ from dateutil.relativedelta import relativedelta
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.utils import timezone, formats
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
with open("version.txt", "r") as f:
|
||||
@@ -128,10 +129,13 @@ def redirect_with_query(request, redirect_url):
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
def generate_username(email):
|
||||
def generate_username(email, claims):
|
||||
# taken from mozilla-django-oidc docs :)
|
||||
|
||||
# Using Python 3 and Django 1.11+, usernames can contain alphanumeric
|
||||
# (ascii and unicode), _, @, +, . and - characters. So we normalize
|
||||
# it and slice at 150 characters.
|
||||
return unicodedata.normalize("NFKC", email)[:150]
|
||||
if settings.OIDC_USERNAME_CLAIM in claims and claims[settings.OIDC_USERNAME_CLAIM]:
|
||||
username = claims[settings.OIDC_USERNAME_CLAIM]
|
||||
else:
|
||||
username = email
|
||||
return unicodedata.normalize("NFKC", username)[:150]
|
||||
|
@@ -105,7 +105,7 @@ Values: `True`, `False` | Default = `False`
|
||||
|
||||
Enables support for OpenID Connect (OIDC) authentication, allowing to use single sign-on (SSO) with OIDC providers.
|
||||
When enabled, this shows a button on the login page that allows users to authenticate using an OIDC provider.
|
||||
Users are associated by the email address provided from the OIDC provider, which is used as the username in linkding.
|
||||
Users are associated by the email address provided from the OIDC provider, which is by default also used as username in linkding. You can configure a custom claim to be used as username with `OIDC_USERNAME_CLAIM`.
|
||||
If there is no user with that email address as username, a new user is created automatically.
|
||||
|
||||
This requires configuring a number of options, which of those you need depends on which OIDC provider you use and how it is configured.
|
||||
@@ -124,6 +124,8 @@ The following options can be configured:
|
||||
- `OIDC_RP_SIGN_ALGO` - The algorithm the OIDC provider uses to sign ID tokens. Default is `RS256`.
|
||||
- `OIDC_USE_PKCE` - Whether to use PKCE for the OIDC flow. Default is `True`.
|
||||
- `OIDC_VERIFY_SSL` - Whether to verify the SSL certificate of the OIDC provider. Set to `False` if using self-signed certificates or custom certificate authority. Default is `True`.
|
||||
- `OIDC_RP_SCOPES` - Scopes asked for on the authorization flow. Default is `oidc email profile`.
|
||||
- `OIDC_USERNAME_CLAIM` - A custom claim to used as username for new accounts, for example `preferred_username`. If the configured claim does not exist or is empty, the email claim is used as fallback. Default is `email`.
|
||||
|
||||
<details>
|
||||
|
||||
|
@@ -194,8 +194,10 @@ if LD_ENABLE_OIDC:
|
||||
OIDC_RP_CLIENT_ID = os.getenv("OIDC_RP_CLIENT_ID")
|
||||
OIDC_RP_CLIENT_SECRET = os.getenv("OIDC_RP_CLIENT_SECRET")
|
||||
OIDC_RP_SIGN_ALGO = os.getenv("OIDC_RP_SIGN_ALGO", "RS256")
|
||||
OIDC_RP_SCOPES = os.getenv("OIDC_RP_SCOPES", "openid email profile")
|
||||
OIDC_USE_PKCE = os.getenv("OIDC_USE_PKCE", True) in (True, "True", "1")
|
||||
OIDC_VERIFY_SSL = os.getenv("OIDC_VERIFY_SSL", True) in (True, "True", "1")
|
||||
OIDC_USERNAME_CLAIM = os.getenv("OIDC_USERNAME_CLAIM", "email")
|
||||
|
||||
# Enable authentication proxy support if configured
|
||||
LD_ENABLE_AUTH_PROXY = os.getenv("LD_ENABLE_AUTH_PROXY", False) in (True, "True", "1")
|
||||
|
Reference in New Issue
Block a user