[WEB-5657] feat: add synchronization configuration for multiple providers in authentication adapter (#8336)

* feat: add sync functionality for OAuth providers

- Implemented `check_sync_enabled` method to verify if sync is enabled for Google, GitHub, GitLab, and Gitea.
- Added `sync_user_data` method to update user details, including first name, last name, display name, and avatar.
- Updated configuration variables to include sync options for each provider.
- Integrated sync check into the login/signup process.

* feat: add sync toggle for OAuth providers in configuration forms

* fix: remove default value for sync options in OAuth configuration forms

* chore: delete old avatar and upload a new one

* chore: update class method

* chore: add email nullable

* refactor: streamline sync check for multiple providers and improve avatar deletion logic

* fix: ensure ENABLE_SYNC configurations default to "0" for Gitea, Github, Gitlab, and Google forms

* fix: simplify toggle switch value handling in ControllerSwitch component

---------

Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
This commit is contained in:
Nikhil 2025-12-22 12:23:39 +05:30 committed by GitHub
parent 4908211fe6
commit c2ce21e56c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 278 additions and 73 deletions

View file

@ -21,6 +21,7 @@ from plane.bgtasks.user_activation_email_task import user_activation_email
from plane.utils.host import base_host
from plane.utils.ip_address import get_client_ip
from plane.utils.exception_logger import log_exception
from plane.settings.storage import S3Storage
class Adapter:
@ -90,9 +91,9 @@ class Adapter:
"""Check if sign up is enabled or not and raise exception if not enabled"""
# Get configuration value
(ENABLE_SIGNUP,) = get_configuration_value(
[{"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")}]
)
(ENABLE_SIGNUP,) = get_configuration_value([
{"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")}
])
# Check if sign up is disabled and invite is present or not
if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists():
@ -108,6 +109,20 @@ class Adapter:
def get_avatar_download_headers(self):
return {}
def check_sync_enabled(self):
"""Check if sync is enabled for the provider"""
provider_config_map = {
"google": "ENABLE_GOOGLE_SYNC",
"github": "ENABLE_GITHUB_SYNC",
"gitlab": "ENABLE_GITLAB_SYNC",
"gitea": "ENABLE_GITEA_SYNC",
}
config_key = provider_config_map.get(self.provider)
if config_key:
(enabled,) = get_configuration_value([{"key": config_key, "default": os.environ.get(config_key, "0")}])
return enabled == "1"
return False
def download_and_upload_avatar(self, avatar_url, user):
"""
Downloads avatar from OAuth provider and uploads to our storage.
@ -156,9 +171,6 @@ class Adapter:
# Generate unique filename
filename = f"{uuid.uuid4().hex}-user-avatar.{extension}"
# Upload to S3/MinIO storage
from plane.settings.storage import S3Storage
storage = S3Storage(request=self.request)
# Create file-like object
@ -208,6 +220,59 @@ class Adapter:
user.save()
return user
def delete_old_avatar(self, user):
"""Delete the old avatar if it exists"""
try:
if user.avatar_asset:
asset = FileAsset.objects.get(pk=user.avatar_asset_id)
storage = S3Storage(request=self.request)
storage.delete_files(object_names=[asset.asset.name])
# Delete the user avatar
asset.delete()
user.avatar_asset = None
user.avatar = ""
user.save()
return
except FileAsset.DoesNotExist:
pass
except Exception as e:
log_exception(e)
return
def sync_user_data(self, user):
# Update user details
first_name = self.user_data.get("user", {}).get("first_name", "")
last_name = self.user_data.get("user", {}).get("last_name", "")
user.first_name = first_name if first_name else ""
user.last_name = last_name if last_name else ""
# Get email
email = self.user_data.get("email")
# Get display name
display_name = self.user_data.get("user", {}).get("display_name")
# If display name is not provided, generate a random display name
if not display_name:
display_name = User.get_display_name(email)
# Set display name
user.display_name = display_name
# Download and upload avatar only if the avatar is different from the one in the storage
avatar = self.user_data.get("user", {}).get("avatar", "")
# Delete the old avatar if it exists
self.delete_old_avatar(user=user)
avatar_asset = self.download_and_upload_avatar(avatar_url=avatar, user=user)
if avatar_asset:
user.avatar_asset = avatar_asset
# If avatar upload fails, set the avatar to the original URL
else:
user.avatar = avatar
user.save()
return user
def complete_login_or_signup(self):
# Get email
email = self.user_data.get("email")
@ -255,6 +320,7 @@ class Adapter:
avatar_asset = self.download_and_upload_avatar(avatar_url=avatar, user=user)
if avatar_asset:
user.avatar_asset = avatar_asset
user.avatar = avatar
# If avatar upload fails, set the avatar to the original URL
else:
user.avatar = avatar
@ -262,6 +328,10 @@ class Adapter:
# Create profile
Profile.objects.create(user=user)
# Check if IDP sync is enabled and user is not signing up
if self.check_sync_enabled() and not is_signup:
user = self.sync_user_data(user=user)
# Save user data
user = self.save_user_data(user=user)