ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: Plane OIDC и план платформы

This commit is contained in:
DCCONSTRUCTIONS
2026-05-04 17:16:47 +03:00
parent 561d1eeef5
commit 55318f14e5
4 changed files with 302 additions and 23 deletions
@@ -81,7 +81,12 @@ class NodeDCOIDCCallbackEndpoint(View):
if not has_required_group(groups):
return oidc_error_redirect(base_url, next_path, "oidc_access_denied")
user = resolve_linked_user(claims=claims, groups=groups, auto_link=config["auto_link_email"])
user = resolve_linked_user(
claims=claims,
groups=groups,
auto_link=config["auto_link_email"],
sync_profile=config["sync_profile"],
)
if user is None or not user.is_active:
return oidc_error_redirect(base_url, next_path, "oidc_user_not_linked")
@@ -109,6 +114,7 @@ def get_oidc_config():
"redirect_uri": redirect_uri,
"scope": os.environ.get("PLANE_OIDC_SCOPE", "openid email profile groups"),
"auto_link_email": os.environ.get("PLANE_OIDC_AUTO_LINK_EMAIL", "0") == "1",
"sync_profile": os.environ.get("PLANE_OIDC_SYNC_PROFILE", "1") == "1",
}
@@ -173,7 +179,7 @@ def has_required_group(groups):
return bool(required_groups.intersection(set(groups)))
def resolve_linked_user(claims, groups, auto_link):
def resolve_linked_user(claims, groups, auto_link, sync_profile):
subject = str(claims.get("sub") or "")
email = str(claims.get("email") or "").strip().lower()
@@ -206,9 +212,53 @@ def resolve_linked_user(claims, groups, auto_link):
user = link.user
user.last_login_medium = OIDC_PROVIDER
user.last_login_time = timezone.now()
user.save(update_fields=["last_login_medium", "last_login_time", "updated_at"])
update_fields = ["last_login_medium", "last_login_time", "updated_at"]
if sync_profile:
update_fields.extend(sync_user_profile_from_claims(user, claims))
user.save(update_fields=list(dict.fromkeys(update_fields)))
return user
def sync_user_profile_from_claims(user, claims):
updated_fields = []
display_name = first_string_claim(claims, "name", "preferred_username")
given_name = first_string_claim(claims, "given_name")
family_name = first_string_claim(claims, "family_name")
avatar_url = first_string_claim(claims, "picture", "avatar_url", "avatar")
if display_name and user.display_name != display_name:
user.display_name = display_name
updated_fields.append("display_name")
if not given_name and display_name:
name_parts = display_name.split(" ", 1)
given_name = name_parts[0]
family_name = family_name or (name_parts[1] if len(name_parts) > 1 else "")
if given_name and user.first_name != given_name:
user.first_name = given_name
updated_fields.append("first_name")
if family_name is not None and user.last_name != family_name:
user.last_name = family_name
updated_fields.append("last_name")
if avatar_url and user.avatar != avatar_url:
user.avatar = avatar_url
updated_fields.append("avatar")
return updated_fields
def first_string_claim(claims, *keys):
for key in keys:
value = claims.get(key)
if isinstance(value, str) and value:
return value
return None
def oidc_error_redirect(base_url, next_path, error_code):
return HttpResponseRedirect(get_safe_redirect_url(base_url=base_url, next_path=next_path, params={"error": error_code}))