diff --git a/backend/eusrr_backend/auth_backends.py b/backend/eusrr_backend/auth_backends.py index e8707dd0..63b75cbb 100644 --- a/backend/eusrr_backend/auth_backends.py +++ b/backend/eusrr_backend/auth_backends.py @@ -498,6 +498,7 @@ class LDAP3Backend(ModelBackend): ) last_name = get(entry, getattr(settings, "LDAP_ATTR_SN", "sn")) or "" email = (get(entry, getattr(settings, "LDAP_ATTR_MAIL", "mail")) or "").lower() + ldap_username = (get(entry, "sAMAccountName") or "").strip() logger.debug( "LDAP attributes read", extra={ @@ -505,6 +506,7 @@ class LDAP3Backend(ModelBackend): "has_first_name": bool(first_name), "has_last_name": bool(last_name), "has_email": bool(email), + "has_username": bool(ldap_username), }, ) @@ -560,6 +562,13 @@ class LDAP3Backend(ModelBackend): if user: changed: List[str] = [] + if ( + ldap_username + and _model_has_field(UserModel, "username") + and (getattr(user, "username", "") or "") != ldap_username + ): + user.username = ldap_username + changed.append("username") if ( _model_has_field(UserModel, "first_name") and user.first_name != first_name @@ -576,6 +585,19 @@ class LDAP3Backend(ModelBackend): ): user.email = email changed.append("email") + if ( + email + and _model_has_field(UserModel, "email_verified") + and not getattr(user, "email_verified", False) + ): + user.email_verified = True + changed.append("email_verified") + if _model_has_field(UserModel, "email_activation_code"): + user.email_activation_code = None + changed.append("email_activation_code") + if _model_has_field(UserModel, "email_activation_sent_at"): + user.email_activation_sent_at = None + changed.append("email_activation_sent_at") if ( PHONE_FIELD and phone_e164 diff --git a/backend/tests/api/auth/test_auth_and_registration.py b/backend/tests/api/auth/test_auth_and_registration.py index af5e2cb0..b291d622 100644 --- a/backend/tests/api/auth/test_auth_and_registration.py +++ b/backend/tests/api/auth/test_auth_and_registration.py @@ -423,14 +423,12 @@ def test_login_by_ldap_username(api, settings): email="ldap.user@example.test", phone_number="+79990000081", password=None, - username="ldap.user", + username="old.cached.login", first_name="Старое", last_name="Имя", is_active=True, send_activation_email=False, ) - user.email_verified = True - user.save(update_fields=["email_verified"]) def attribute(value): return SimpleNamespace(value=value) @@ -472,8 +470,12 @@ def test_login_by_ldap_username(api, settings): assert "(sAMAccountName=ldap.user)" in search_filter user.refresh_from_db() + assert user.username == "ldap.user" assert user.first_name == "Иван" assert user.last_name == "Иванов" + assert user.email_verified is True + assert user.email_activation_code is None + assert user.email_activation_sent_at is None assert user.is_ldap_managed is True diff --git a/docs/guides/OIDC.md b/docs/guides/OIDC.md index c5b06d06..3a3ea715 100644 --- a/docs/guides/OIDC.md +++ b/docs/guides/OIDC.md @@ -88,7 +88,42 @@ python manage.py provision_oidc_client \ ## Настройка Gitea -В `app.ini`: +В `app.ini` включить закрытый режим экземпляра. Значение +`DISABLE_REGISTRATION = false` здесь намеренное: Gitea 1.23 применяет +`ALLOW_ONLY_EXTERNAL_REGISTRATION` только при разрешённом механизме +регистрации. В результате самостоятельной формы регистрации нет, но LDAP и +корпоративный OIDC могут создать связанную учётную запись: + +```ini +[server] +LANDING_PAGE = login + +[service] +DISABLE_REGISTRATION = false +ALLOW_ONLY_INTERNAL_REGISTRATION = false +ALLOW_ONLY_EXTERNAL_REGISTRATION = true +REQUIRE_SIGNIN_VIEW = true +SHOW_REGISTRATION_BUTTON = false +FORCE_PRIVATE = true +DEFAULT_PRIVATE = private +DEFAULT_PUSH_CREATE_PRIVATE = true + +[openid] +ENABLE_OPENID_SIGNIN = false +ENABLE_OPENID_SIGNUP = false +``` + +`REQUIRE_SIGNIN_VIEW` закрывает анонимный просмотр веб-страниц и +неаутентифицированное чтение Git по HTTP даже для репозиториев, которые раньше +были помечены публичными. `FORCE_PRIVATE` и `DEFAULT_PRIVATE` не меняют старые +репозитории, но не позволяют создавать новые публичные. Перед публикацией +экземпляра наружу отдельно проверьте список существующих публичных +репозиториев и при необходимости переведите их в private. + +Локальную форму входа пока оставить включённой: она нужна для существующих +локальных и LDAP-учёток, первичной привязки и аварийного администратора. + +Для OAuth-клиентов в том же `app.ini`: ```ini [oauth2_client] @@ -115,6 +150,11 @@ REGISTER_EMAIL_CONFIRM = false До переключения пользователей оставить локальный вход Gitea включённым и сохранить отдельную аварийную локальную учётную запись администратора. `ACCOUNT_LINKING = login` не удаляет и не разлогинивает существующие локальные учётки: при первом конфликте username/email Gitea попросит один раз подтвердить привязку существующим локальным входом. +LDAP-пользователь после успешного входа в портал получает в OIDC claim +`preferred_username` свой `sAMAccountName`. Успешная LDAP-проверка также +считает корпоративный адрес подтверждённым; обычная регистрация по-прежнему +требует штатного подтверждения email. + Gitea 1.23.x не отправляет PKCE challenge для confidential OIDC-клиента, поэтому портал временно допускает Authorization Code без PKCE для совместимости. После обновления Gitea и проверки поддержки PKCE следует включить `PKCE_REQUIRED` в настройках провайдера. ## Claims и группы