[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:
parent
4908211fe6
commit
c2ce21e56c
10 changed files with 278 additions and 73 deletions
|
|
@ -12,6 +12,8 @@ import { CodeBlock } from "@/components/common/code-block";
|
|||
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
|
||||
import { ControllerSwitch } from "@/components/common/controller-switch";
|
||||
import type { TCopyField } from "@/components/common/copy-field";
|
||||
import { CopyField } from "@/components/common/copy-field";
|
||||
// hooks
|
||||
|
|
@ -40,6 +42,7 @@ export function InstanceGiteaConfigForm(props: Props) {
|
|||
GITEA_HOST: config["GITEA_HOST"] || "https://gitea.com",
|
||||
GITEA_CLIENT_ID: config["GITEA_CLIENT_ID"],
|
||||
GITEA_CLIENT_SECRET: config["GITEA_CLIENT_SECRET"],
|
||||
ENABLE_GITEA_SYNC: config["ENABLE_GITEA_SYNC"] || "0",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -103,6 +106,11 @@ export function InstanceGiteaConfigForm(props: Props) {
|
|||
},
|
||||
];
|
||||
|
||||
const GITEA_FORM_SWITCH_FIELD: TControllerSwitchFormField<GiteaConfigFormValues> = {
|
||||
name: "ENABLE_GITEA_SYNC",
|
||||
label: "Gitea",
|
||||
};
|
||||
|
||||
const GITEA_SERVICE_FIELD: TCopyField[] = [
|
||||
{
|
||||
key: "Callback_URI",
|
||||
|
|
@ -129,20 +137,22 @@ export function InstanceGiteaConfigForm(props: Props) {
|
|||
const onSubmit = async (formData: GiteaConfigFormValues) => {
|
||||
const payload: Partial<GiteaConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your Gitea authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITEA_HOST: response.find((item) => item.key === "GITEA_HOST")?.value,
|
||||
GITEA_CLIENT_ID: response.find((item) => item.key === "GITEA_CLIENT_ID")?.value,
|
||||
GITEA_CLIENT_SECRET: response.find((item) => item.key === "GITEA_CLIENT_SECRET")?.value,
|
||||
});
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
try {
|
||||
const response = await updateInstanceConfigurations(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your Gitea authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITEA_HOST: response.find((item) => item.key === "GITEA_HOST")?.value,
|
||||
GITEA_CLIENT_ID: response.find((item) => item.key === "GITEA_CLIENT_ID")?.value,
|
||||
GITEA_CLIENT_SECRET: response.find((item) => item.key === "GITEA_CLIENT_SECRET")?.value,
|
||||
ENABLE_GITEA_SYNC: response.find((item) => item.key === "ENABLE_GITEA_SYNC")?.value,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
|
|
@ -176,12 +186,13 @@ export function InstanceGiteaConfigForm(props: Props) {
|
|||
required={field.required}
|
||||
/>
|
||||
))}
|
||||
<ControllerSwitch control={control} field={GITEA_FORM_SWITCH_FIELD} />
|
||||
<div className="flex flex-col gap-1 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import { API_BASE_URL } from "@plane/constants";
|
|||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
||||
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
|
||||
import { ControllerSwitch } from "@/components/common/controller-switch";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import type { TCopyField } from "@/components/common/copy-field";
|
||||
import { CopyField } from "@/components/common/copy-field";
|
||||
|
|
@ -43,6 +43,7 @@ export function InstanceGithubConfigForm(props: Props) {
|
|||
GITHUB_CLIENT_ID: config["GITHUB_CLIENT_ID"],
|
||||
GITHUB_CLIENT_SECRET: config["GITHUB_CLIENT_SECRET"],
|
||||
GITHUB_ORGANIZATION_ID: config["GITHUB_ORGANIZATION_ID"],
|
||||
ENABLE_GITHUB_SYNC: config["ENABLE_GITHUB_SYNC"] || "0",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -104,6 +105,11 @@ export function InstanceGithubConfigForm(props: Props) {
|
|||
},
|
||||
];
|
||||
|
||||
const GITHUB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GithubConfigFormValues> = {
|
||||
name: "ENABLE_GITHUB_SYNC",
|
||||
label: "GitHub",
|
||||
};
|
||||
|
||||
const GITHUB_COMMON_SERVICE_DETAILS: TCopyField[] = [
|
||||
{
|
||||
key: "Origin_URL",
|
||||
|
|
@ -152,20 +158,22 @@ export function InstanceGithubConfigForm(props: Props) {
|
|||
const onSubmit = async (formData: GithubConfigFormValues) => {
|
||||
const payload: Partial<GithubConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your GitHub authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
|
||||
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
|
||||
GITHUB_ORGANIZATION_ID: response.find((item) => item.key === "GITHUB_ORGANIZATION_ID")?.value,
|
||||
});
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
try {
|
||||
const response = await updateInstanceConfigurations(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your GitHub authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
|
||||
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
|
||||
GITHUB_ORGANIZATION_ID: response.find((item) => item.key === "GITHUB_ORGANIZATION_ID")?.value,
|
||||
ENABLE_GITHUB_SYNC: response.find((item) => item.key === "ENABLE_GITHUB_SYNC")?.value,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
|
|
@ -199,12 +207,13 @@ export function InstanceGithubConfigForm(props: Props) {
|
|||
required={field.required}
|
||||
/>
|
||||
))}
|
||||
<ControllerSwitch control={control} field={GITHUB_FORM_SWITCH_FIELD} />
|
||||
<div className="flex flex-col gap-1 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import { API_BASE_URL } from "@plane/constants";
|
|||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
|
||||
import { ControllerSwitch } from "@/components/common/controller-switch";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import type { TCopyField } from "@/components/common/copy-field";
|
||||
import { CopyField } from "@/components/common/copy-field";
|
||||
|
|
@ -41,6 +42,7 @@ export function InstanceGitlabConfigForm(props: Props) {
|
|||
GITLAB_HOST: config["GITLAB_HOST"],
|
||||
GITLAB_CLIENT_ID: config["GITLAB_CLIENT_ID"],
|
||||
GITLAB_CLIENT_SECRET: config["GITLAB_CLIENT_SECRET"],
|
||||
ENABLE_GITLAB_SYNC: config["ENABLE_GITLAB_SYNC"] || "0",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -108,6 +110,11 @@ export function InstanceGitlabConfigForm(props: Props) {
|
|||
},
|
||||
];
|
||||
|
||||
const GITLAB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GitlabConfigFormValues> = {
|
||||
name: "ENABLE_GITLAB_SYNC",
|
||||
label: "GitLab",
|
||||
};
|
||||
|
||||
const GITLAB_SERVICE_FIELD: TCopyField[] = [
|
||||
{
|
||||
key: "Callback_URL",
|
||||
|
|
@ -134,20 +141,22 @@ export function InstanceGitlabConfigForm(props: Props) {
|
|||
const onSubmit = async (formData: GitlabConfigFormValues) => {
|
||||
const payload: Partial<GitlabConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your GitLab authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITLAB_HOST: response.find((item) => item.key === "GITLAB_HOST")?.value,
|
||||
GITLAB_CLIENT_ID: response.find((item) => item.key === "GITLAB_CLIENT_ID")?.value,
|
||||
GITLAB_CLIENT_SECRET: response.find((item) => item.key === "GITLAB_CLIENT_SECRET")?.value,
|
||||
});
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
try {
|
||||
const response = await updateInstanceConfigurations(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your GitLab authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GITLAB_HOST: response.find((item) => item.key === "GITLAB_HOST")?.value,
|
||||
GITLAB_CLIENT_ID: response.find((item) => item.key === "GITLAB_CLIENT_ID")?.value,
|
||||
GITLAB_CLIENT_SECRET: response.find((item) => item.key === "GITLAB_CLIENT_SECRET")?.value,
|
||||
ENABLE_GITLAB_SYNC: response.find((item) => item.key === "ENABLE_GITLAB_SYNC")?.value,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
|
|
@ -181,12 +190,13 @@ export function InstanceGitlabConfigForm(props: Props) {
|
|||
required={field.required}
|
||||
/>
|
||||
))}
|
||||
<ControllerSwitch control={control} field={GITLAB_FORM_SWITCH_FIELD} />
|
||||
<div className="flex flex-col gap-1 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import { API_BASE_URL } from "@plane/constants";
|
|||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { CodeBlock } from "@/components/common/code-block";
|
||||
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
|
||||
import type { TControllerInputFormField } from "@/components/common/controller-input";
|
||||
import type { TControllerSwitchFormField } from "@/components/common/controller-switch";
|
||||
import { ControllerSwitch } from "@/components/common/controller-switch";
|
||||
import { ControllerInput } from "@/components/common/controller-input";
|
||||
import type { TCopyField } from "@/components/common/copy-field";
|
||||
import { CopyField } from "@/components/common/copy-field";
|
||||
|
|
@ -41,6 +42,7 @@ export function InstanceGoogleConfigForm(props: Props) {
|
|||
defaultValues: {
|
||||
GOOGLE_CLIENT_ID: config["GOOGLE_CLIENT_ID"],
|
||||
GOOGLE_CLIENT_SECRET: config["GOOGLE_CLIENT_SECRET"],
|
||||
ENABLE_GOOGLE_SYNC: config["ENABLE_GOOGLE_SYNC"] || "0",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -93,6 +95,11 @@ export function InstanceGoogleConfigForm(props: Props) {
|
|||
},
|
||||
];
|
||||
|
||||
const GOOGLE_FORM_SWITCH_FIELD: TControllerSwitchFormField<GoogleConfigFormValues> = {
|
||||
name: "ENABLE_GOOGLE_SYNC",
|
||||
label: "Google",
|
||||
};
|
||||
|
||||
const GOOGLE_COMMON_SERVICE_DETAILS: TCopyField[] = [
|
||||
{
|
||||
key: "Origin_URL",
|
||||
|
|
@ -140,19 +147,21 @@ export function InstanceGoogleConfigForm(props: Props) {
|
|||
const onSubmit = async (formData: GoogleConfigFormValues) => {
|
||||
const payload: Partial<GoogleConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your Google authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value,
|
||||
GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value,
|
||||
});
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
try {
|
||||
const response = await updateInstanceConfigurations(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Done!",
|
||||
message: "Your Google authentication is configured. You should test it now.",
|
||||
});
|
||||
reset({
|
||||
GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value,
|
||||
GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value,
|
||||
ENABLE_GOOGLE_SYNC: response.find((item) => item.key === "ENABLE_GOOGLE_SYNC")?.value,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
|
|
@ -186,12 +195,13 @@ export function InstanceGoogleConfigForm(props: Props) {
|
|||
required={field.required}
|
||||
/>
|
||||
))}
|
||||
<ControllerSwitch control={control} field={GOOGLE_FORM_SWITCH_FIELD} />
|
||||
<div className="flex flex-col gap-1 pt-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={(e) => void handleSubmit(onSubmit)(e)}
|
||||
loading={isSubmitting}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
|
|
|
|||
40
apps/admin/core/components/common/controller-switch.tsx
Normal file
40
apps/admin/core/components/common/controller-switch.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
// plane internal packages
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
|
||||
type Props<T extends FieldValues = FieldValues> = {
|
||||
control: Control<T>;
|
||||
field: TControllerSwitchFormField<T>;
|
||||
};
|
||||
|
||||
export type TControllerSwitchFormField<T extends FieldValues = FieldValues> = {
|
||||
name: FieldPath<T>;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export function ControllerSwitch<T extends FieldValues>(props: Props<T>) {
|
||||
const {
|
||||
control,
|
||||
field: { name, label },
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<h4 className="text-sm text-custom-text-300">Refresh user attributes from {label} during sign in</h4>
|
||||
<div className="relative">
|
||||
<Controller
|
||||
control={control}
|
||||
name={name as FieldPath<T>}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ToggleSwitch
|
||||
value={Boolean(parseInt(value))}
|
||||
onChange={() => (parseInt(value) ? onChange("0") : onChange("1"))}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -172,6 +172,16 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||
|
||||
super(User, self).save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls, email):
|
||||
if not email:
|
||||
return "".join(random.choice(string.ascii_letters) for _ in range(6))
|
||||
return (
|
||||
email.split("@")[0]
|
||||
if len(email.split("@")) == 2
|
||||
else "".join(random.choice(string.ascii_letters) for _ in range(6))
|
||||
)
|
||||
|
||||
|
||||
class Profile(TimeAuditModel):
|
||||
SUNDAY = 0
|
||||
|
|
|
|||
|
|
@ -187,3 +187,15 @@ class S3Storage(S3Boto3Storage):
|
|||
except ClientError as e:
|
||||
log_exception(e)
|
||||
return False
|
||||
|
||||
def delete_files(self, object_names):
|
||||
"""Delete an S3 object"""
|
||||
try:
|
||||
self.s3_client.delete_objects(
|
||||
Bucket=self.aws_storage_bucket_name,
|
||||
Delete={"Objects": [{"Key": object_name} for object_name in object_names]},
|
||||
)
|
||||
return True
|
||||
except ClientError as e:
|
||||
log_exception(e)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ google_config_variables = [
|
|||
"category": "GOOGLE",
|
||||
"is_encrypted": True,
|
||||
},
|
||||
{
|
||||
"key": "ENABLE_GOOGLE_SYNC",
|
||||
"value": os.environ.get("ENABLE_GOOGLE_SYNC", "0"),
|
||||
"category": "GOOGLE",
|
||||
"is_encrypted": False,
|
||||
},
|
||||
]
|
||||
|
||||
github_config_variables = [
|
||||
|
|
@ -65,6 +71,12 @@ github_config_variables = [
|
|||
"category": "GITHUB",
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "ENABLE_GITHUB_SYNC",
|
||||
"value": os.environ.get("ENABLE_GITHUB_SYNC", "0"),
|
||||
"category": "GITHUB",
|
||||
"is_encrypted": False,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -87,6 +99,12 @@ gitlab_config_variables = [
|
|||
"category": "GITLAB",
|
||||
"is_encrypted": True,
|
||||
},
|
||||
{
|
||||
"key": "ENABLE_GITLAB_SYNC",
|
||||
"value": os.environ.get("ENABLE_GITLAB_SYNC", "0"),
|
||||
"category": "GITLAB",
|
||||
"is_encrypted": False,
|
||||
},
|
||||
]
|
||||
|
||||
gitea_config_variables = [
|
||||
|
|
@ -114,6 +132,12 @@ gitea_config_variables = [
|
|||
"category": "GITEA",
|
||||
"is_encrypted": True,
|
||||
},
|
||||
{
|
||||
"key": "ENABLE_GITEA_SYNC",
|
||||
"value": os.environ.get("ENABLE_GITEA_SYNC", "0"),
|
||||
"category": "GITEA",
|
||||
"is_encrypted": False,
|
||||
},
|
||||
]
|
||||
|
||||
smtp_config_variables = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue