diff --git a/src/spaceone/identity/manager/email_manager.py b/src/spaceone/identity/manager/email_manager.py
index ea9ebe33..8c91c8a7 100644
--- a/src/spaceone/identity/manager/email_manager.py
+++ b/src/spaceone/identity/manager/email_manager.py
@@ -22,21 +22,25 @@
"reset_password": "Reset your password",
"temp_password": "Your password has been changed",
"verify_email": "Verify your notification email",
+ "invite_external_user": "You've been invited to join.",
},
"ko": {
"reset_password": "비밀번호 재설정 안내",
"temp_password": "임시 비밀번호 발급 안내",
"verify_email": "알림전용 이메일 계정 인증 안내",
+ "invite_external_user": "계정 초대 안내.",
},
"en": {
"reset_password": "Reset your password",
"temp_password": "Your password has been changed",
"verify_email": "Verify your notification email",
+ "invite_external_user": "You've been invited to join.",
},
"ja": {
"reset_password": "パスワードリセットのご案内",
"temp_password": "仮パスワード発行のご案内",
"verify_email": "通知メールアカウント認証のご案内",
+ "invite_external_user": "参加するように招待されました",
},
}
@@ -63,7 +67,7 @@ def send_reset_password_email(self, user_id, email, reset_password_link, languag
self.smtp_connector.send_email(email, subject, email_contents)
def send_temporary_password_email(
- self, user_id, email, console_link, temp_password, language
+ self, user_id, email, console_link, temp_password, language
):
service_name = self._get_service_name()
language_map_info = LANGUAGE_MAPPER.get(language, "default")
@@ -80,7 +84,7 @@ def send_temporary_password_email(
self.smtp_connector.send_email(email, subject, email_contents)
def send_reset_password_email_when_user_added(
- self, user_id, email, reset_password_link, language
+ self, user_id, email, reset_password_link, language
):
service_name = self._get_service_name()
language_map_info = LANGUAGE_MAPPER.get(language, "default")
@@ -98,7 +102,7 @@ def send_reset_password_email_when_user_added(
self.smtp_connector.send_email(email, subject, email_contents)
def send_temporary_password_email_when_user_added(
- self, user_id, email, console_link, temp_password, language
+ self, user_id, email, console_link, temp_password, language
):
service_name = self._get_service_name()
language_map_info = LANGUAGE_MAPPER.get(language, "default")
@@ -114,6 +118,29 @@ def send_temporary_password_email_when_user_added(
self.smtp_connector.send_email(email, subject, email_contents)
+ def send_invite_email_when_external_user_added(
+ self,
+ user_id: str,
+ email: str,
+ console_link: str,
+ language: str,
+ auth_type: str = "EXTERNAL",
+ ):
+ service_name = self._get_service_name()
+ language_map_info = LANGUAGE_MAPPER.get(language, "default")
+
+ template = JINJA_ENV.get_template(f"sso_invite_user_link_{language}.html")
+
+ email_contents = template.render(
+ user_name=user_id,
+ auth_type=auth_type,
+ service_name=service_name,
+ login_link=console_link,
+ )
+
+ subject = f'[{service_name}] {language_map_info["invite_external_user"]}'
+ self.smtp_connector.send_email(email, subject, email_contents)
+
def send_verification_email(self, user_id, email, verification_code, language):
service_name = self._get_service_name()
language_map_info = LANGUAGE_MAPPER.get(language, "default")
diff --git a/src/spaceone/identity/service/job_service.py b/src/spaceone/identity/service/job_service.py
index 1ee6a1c1..0373d7c6 100644
--- a/src/spaceone/identity/service/job_service.py
+++ b/src/spaceone/identity/service/job_service.py
@@ -65,7 +65,7 @@ def create_jobs_by_trusted_account(self, params: dict):
current_hour = params.get("current_hour", datetime.utcnow().hour)
for trusted_account_vo in self._get_all_schedule_enabled_trusted_accounts(
- current_hour
+ current_hour
):
try:
self.create_service_account_job(trusted_account_vo, {})
@@ -334,7 +334,7 @@ def sync_service_accounts(self, params: dict) -> None:
)
def create_service_account_job(
- self, trusted_account_vo: TrustedAccount, job_options: dict
+ self, trusted_account_vo: TrustedAccount, job_options: dict
) -> Union[Job, dict]:
resource_group = trusted_account_vo.resource_group
provider = trusted_account_vo.provider
@@ -437,10 +437,10 @@ def _get_trusted_secret_data(self, trusted_secret_id: str, domain_id: str) -> di
return secret_data
def _check_duplicate_job(
- self,
- domain_id: str,
- trusted_account_id: str,
- this_job_vo: Job,
+ self,
+ domain_id: str,
+ trusted_account_id: str,
+ this_job_vo: Job,
) -> bool:
query = {
"filter": [
@@ -464,7 +464,7 @@ def _check_duplicate_job(
return False
def _is_job_failed(
- self, job_id: str, domain_id: str, workspace_id: str = None
+ self, job_id: str, domain_id: str, workspace_id: str = None
) -> bool:
job_vo: Job = self.job_mgr.get_job(domain_id, job_id, workspace_id)
@@ -474,10 +474,10 @@ def _is_job_failed(
return False
def _close_job(
- self,
- job_id: str,
- domain_id: str,
- workspace_id: str = None,
+ self,
+ job_id: str,
+ domain_id: str,
+ workspace_id: str = None,
):
job_vo: Job = self.job_mgr.get_job(domain_id, job_id, workspace_id)
if job_vo.status == "IN_PROGRESS":
@@ -486,7 +486,7 @@ def _close_job(
self.job_mgr.update_job_by_vo({"finished_at": datetime.utcnow()}, job_vo)
def _create_workspace(
- self, domain_id: str, trusted_account_id: str, location_info: dict
+ self, domain_id: str, trusted_account_id: str, location_info: dict
) -> Workspace:
name = location_info.get("name")
reference_id = location_info.get("resource_id")
@@ -503,9 +503,6 @@ def _create_workspace(
if workspace_vos:
workspace_vo = workspace_vos[0]
- if workspace_vo.name != name:
- params["name"] = name
-
if not workspace_vo.references:
params["references"] = [reference_id]
elif reference_id not in workspace_vo.references:
@@ -535,12 +532,12 @@ def _create_workspace(
return workspace_vo
def _create_project_group(
- self,
- domain_id: str,
- workspace_id: str,
- trusted_account_id: str,
- location_info: dict,
- parent_group_id: str = None,
+ self,
+ domain_id: str,
+ workspace_id: str,
+ trusted_account_id: str,
+ location_info: dict,
+ parent_group_id: str = None,
) -> ProjectGroup:
name = location_info["name"]
reference_id = location_info["resource_id"]
@@ -590,14 +587,14 @@ def _create_project_group(
return project_group_vo
def _create_project(
- self,
- result: dict,
- domain_id: str,
- workspace_id: str,
- trusted_account_id: str,
- project_group_id: str = None,
- sync_options: dict = None,
- project_type: str = "PRIVATE",
+ self,
+ result: dict,
+ domain_id: str,
+ workspace_id: str,
+ trusted_account_id: str,
+ project_group_id: str = None,
+ sync_options: dict = None,
+ project_type: str = "PRIVATE",
) -> Project:
name = result["name"]
reference_id = result["resource_id"]
@@ -636,13 +633,13 @@ def _create_project(
return project_vo
def _create_service_account(
- self,
- result: dict,
- project_vo: Project,
- trusted_account_id: str,
- trusted_secret_id: str,
- provider: str,
- sync_options: dict = None,
+ self,
+ result: dict,
+ project_vo: Project,
+ trusted_account_id: str,
+ trusted_secret_id: str,
+ provider: str,
+ sync_options: dict = None,
) -> Union[ServiceAccount, None]:
domain_id = project_vo.domain_id
workspace_id = project_vo.workspace_id
@@ -728,7 +725,7 @@ def _create_service_account(
return service_account_vo
def _remove_old_reference_id_from_workspace(
- self, domain_id: str, workspace_id: str, reference_id: str
+ self, domain_id: str, workspace_id: str, reference_id: str
) -> None:
query = {
"filter": [
diff --git a/src/spaceone/identity/service/user_service.py b/src/spaceone/identity/service/user_service.py
index 8946e047..c9bdf9ff 100644
--- a/src/spaceone/identity/service/user_service.py
+++ b/src/spaceone/identity/service/user_service.py
@@ -69,7 +69,7 @@ def create_user(self, params: dict) -> User:
reset_password = params["reset_password"]
domain_id = params["domain_id"]
email = params.get("email")
- language = params.get("language") or "en"
+ language = params.get("language", "en") or "en"
if reset_password:
self._check_reset_password_eligibility(user_id, auth_type, email)
@@ -86,7 +86,7 @@ def create_user(self, params: dict) -> User:
identity_conf = config.get_global("IDENTITY", {}) or {}
token_conf = identity_conf.get("token", {})
timeout = token_conf.get("invite_token_timeout", 604800)
-
+
token = self._issue_temporary_token(user_id, domain_id, timeout)
reset_password_link = self._get_console_sso_url(
domain_id, token["access_token"]
@@ -107,6 +107,20 @@ def create_user(self, params: dict) -> User:
)
else:
user_vo = self.user_mgr.create_user(params)
+ if (
+ auth_type == "EXTERNAL"
+ and self._check_invite_external_user_eligibility(user_id, user_id)
+ ):
+ email_manager = EmailManager()
+ console_link = self._get_console_url(domain_id)
+
+ email_manager.send_invite_email_when_external_user_added(
+ user_id,
+ user_id,
+ console_link,
+ language,
+ user_vo.auth_type,
+ )
return user_vo
@@ -237,7 +251,7 @@ def disable_mfa(self, params: UserDisableMFARequest) -> Union[UserResponse, dict
@transaction(permission="identity:User.write", role_types=["DOMAIN_ADMIN"])
@convert_model
def set_required_actions(
- self, params: UserSetRequiredActionsRequest
+ self, params: UserSetRequiredActionsRequest
) -> Union[UserResponse, dict]:
"""Set required actions
@@ -408,7 +422,9 @@ def _get_domain_name(self, domain_id: str) -> str:
domain_vo = self.domain_mgr.get_domain(domain_id)
return domain_vo.name
- def _issue_temporary_token(self, user_id: str, domain_id: str, timeout: int = None) -> dict:
+ def _issue_temporary_token(
+ self, user_id: str, domain_id: str, timeout: int = None
+ ) -> dict:
if timeout is None:
identity_conf = config.get_global("IDENTITY", {}) or {}
token_conf = identity_conf.get("token", {})
@@ -444,7 +460,7 @@ def _get_console_url(self, domain_id):
return console_domain.format(domain_name=domain_name)
@staticmethod
- def _check_reset_password_eligibility(user_id, auth_type, email):
+ def _check_reset_password_eligibility(user_id: str, auth_type: str, email: str):
if auth_type == "EXTERNAL":
raise ERROR_UNABLE_TO_RESET_PASSWORD_IN_EXTERNAL_AUTH(user_id=user_id)
elif email is None:
@@ -460,8 +476,26 @@ def _generate_temporary_password():
for _ in range(12)
)
if (
- re.search("[a-z]", random_password)
- and re.search("[A-Z]", random_password)
- and re.search("[0-9]", random_password)
+ re.search("[a-z]", random_password)
+ and re.search("[A-Z]", random_password)
+ and re.search("[0-9]", random_password)
):
return random_password
+
+ @staticmethod
+ def _check_invite_external_user_eligibility(user_id: str, email: str) -> bool:
+ rule = r"[^@]+@[^@]+\.[^@]+"
+
+ if email is None:
+ _LOGGER.debug(
+ f"[_check_invite_external_user_eligibility] email is None (user_id={user_id})"
+ )
+ return False
+
+ if not re.match(rule, email):
+ _LOGGER.debug(
+ f"[_check_invite_external_user_eligibility] email format is incorrect (user_id={user_id}, email={email})"
+ )
+ return False
+
+ return True
diff --git a/src/spaceone/identity/template/sso_invite_user_link_en.html b/src/spaceone/identity/template/sso_invite_user_link_en.html
new file mode 100644
index 00000000..5a4a138b
--- /dev/null
+++ b/src/spaceone/identity/template/sso_invite_user_link_en.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We noticed your account was newly added by your domain administrator.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ Hi {{user_name}},
+
+
+
+ We noticed your account was newly added by your domain administrator. To continue using {{service_name}}, Please access via the link below.
- Login Account : {{auth_type}}
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ Go to Sign In
+ |
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Sincerely yours, {{service_name}}
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ Copyright 2024 © MEGAZONE CLOUD Corp. All Right
+ Reserved.
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
diff --git a/src/spaceone/identity/template/sso_invite_user_link_jp.html b/src/spaceone/identity/template/sso_invite_user_link_jp.html
new file mode 100644
index 00000000..809799c7
--- /dev/null
+++ b/src/spaceone/identity/template/sso_invite_user_link_jp.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ お客様のアカウントが、ドメイン管理者によって{{service_name}}に新しく追加されたことを検知しました。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ {{user_name}}さん こんにちは、
+
+
+
+ お客様のアカウントが、ドメイン管理者によって{{service_name}}に新しく追加されたことを検知しました。 {{service_name}}を引き続きご利用いただくには、以下のリンクからアクセスしてください。
-ログインアカウント : {{auth_type}}
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ 今後とも{{service_name}}をよろしくお願い致します。
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ Copyright 2024 © MEGAZONE CLOUD Corp. All Right
+ Reserved.
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
diff --git a/src/spaceone/identity/template/sso_invite_user_link_ko.html b/src/spaceone/identity/template/sso_invite_user_link_ko.html
new file mode 100644
index 00000000..5761d05a
--- /dev/null
+++ b/src/spaceone/identity/template/sso_invite_user_link_ko.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 고객님의 계정이 도메인 관리자에 의해 {{service_name}}에 신규 추가 또는 업데이트 되었음이 확인되었습니다.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ {{user_email}}님 안녕하세요,
+
+
+
+ 고객님의 계정이 도메인 관리자에 의해 {{service_name}}에 신규 추가 되었습니다. 계속해서 {{service_name}}을 사용하시려면, 아래 링크를 통해 접속해주세요.
-로그인 계정 : {{auth_type}}
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ 감사합니다. {{service_name}}
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+ Copyright 2024 © MEGAZONE CLOUD Corp. All Right
+ Reserved.
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+