11import logging
22from typing import List , Tuple
3+ from collections import OrderedDict
34
4- from spaceone .core import cache
5+ from spaceone .core import cache , config , utils
56from spaceone .core .auth .jwt import JWTAuthenticator , JWTUtil
67from spaceone .core .service import *
78from spaceone .core .service .utils import *
89
10+
911from spaceone .identity .error .error_authentication import *
1012from spaceone .identity .error .error_domain import ERROR_DOMAIN_STATE
1113from spaceone .identity .error .error_mfa import *
@@ -48,6 +50,7 @@ def __init__(self, *args, **kwargs):
4850 self .project_mgr = ProjectManager ()
4951 self .project_group_mgr = ProjectGroupManager ()
5052 self .workspace_mgr = WorkspaceManager ()
53+ self ._load_conf ()
5154
5255 @transaction ()
5356 @convert_model
@@ -78,34 +81,51 @@ def issue(self, params: TokenIssueRequest) -> Union[TokenResponse, dict]:
7881 # Check Domain state is ENABLED
7982 self ._check_domain_state (domain_id )
8083
81- token_mgr = TokenManager .get_token_manager_by_auth_type (params .auth_type )
82- token_mgr .authenticate (
83- domain_id , verify_code = verify_code , credentials = credentials
84- )
84+ try :
85+ token_mgr = TokenManager .get_token_manager_by_auth_type (params .auth_type )
86+ token_mgr .authenticate (
87+ domain_id , verify_code = verify_code , credentials = credentials
88+ )
89+ except Exception as e :
90+ self ._increment_issue_attempts (domain_id , credentials )
91+ raise e
8592
8693 user_vo = token_mgr .user
8794 user_mfa = user_vo .mfa .to_dict () if user_vo .mfa else {}
88- mfa_type = user_mfa .get (' mfa_type' )
95+ mfa_type = user_mfa .get (" mfa_type" )
8996 permissions = self ._get_permissions_from_required_actions (user_vo )
9097
9198 mfa_user_id = user_vo .user_id
9299
93100 if self ._check_login_protocol_with_user_auth_type (params .auth_type , domain_id ):
94- if user_mfa .get ("state" , "DISABLED" ) == "ENABLED" and params .auth_type != "MFA" :
101+ if (
102+ user_mfa .get ("state" , "DISABLED" ) == "ENABLED"
103+ and params .auth_type != "MFA"
104+ ):
95105 mfa_manager = MFAManager .get_manager_by_mfa_type (mfa_type )
96106 if mfa_type == "EMAIL" :
97107 mfa_email = user_mfa ["options" ].get ("email" )
98108 mfa_manager .send_mfa_authentication_email (
99- user_vo .user_id , domain_id , mfa_email , user_vo .language , credentials
109+ user_vo .user_id ,
110+ domain_id ,
111+ mfa_email ,
112+ user_vo .language ,
113+ credentials ,
100114 )
101115 mfa_user_id = mfa_email
102116
103117 elif mfa_type == "OTP" :
104- secret_manager : SecretManager = self .locator .get_manager (SecretManager )
118+ secret_manager : SecretManager = self .locator .get_manager (
119+ SecretManager
120+ )
105121 user_secret_id = user_mfa ["options" ].get ("user_secret_id" )
106- otp_secret_key = secret_manager .get_user_otp_secret_key (user_secret_id , domain_id )
122+ otp_secret_key = secret_manager .get_user_otp_secret_key (
123+ user_secret_id , domain_id
124+ )
107125
108- mfa_manager .set_cache_otp_mfa_secret_key (otp_secret_key , user_vo .user_id , domain_id , credentials )
126+ mfa_manager .set_cache_otp_mfa_secret_key (
127+ otp_secret_key , user_vo .user_id , domain_id , credentials
128+ )
109129
110130 raise ERROR_MFA_REQUIRED (user_id = mfa_user_id , mfa_type = mfa_type )
111131
@@ -117,6 +137,8 @@ def issue(self, params: TokenIssueRequest) -> Union[TokenResponse, dict]:
117137 permissions = permissions ,
118138 )
119139
140+ self ._clear_issue_attempts (domain_id , credentials )
141+
120142 return TokenResponse (** token_info )
121143
122144 @transaction ()
@@ -396,11 +418,17 @@ def _get_user_projects(
396418
397419 return user_projects
398420
399- def _check_login_protocol_with_user_auth_type (self , user_auth_type : str , domain_id : str ) -> bool :
421+ def _check_login_protocol_with_user_auth_type (
422+ self , user_auth_type : str , domain_id : str
423+ ) -> bool :
400424 if user_auth_type == "EXTERNAL" :
401425 domain : Domain = self .domain_mgr .get_domain (domain_id )
402426 external_auth_mgr = ExternalAuthManager ()
403- external_metadata_protocol = external_auth_mgr .get_auth_info (domain ).get ('metadata' , {}).get ('protocol' )
427+ external_metadata_protocol = (
428+ external_auth_mgr .get_auth_info (domain )
429+ .get ("metadata" , {})
430+ .get ("protocol" )
431+ )
404432
405433 if external_metadata_protocol == "saml" :
406434 return False
@@ -413,3 +441,36 @@ def _check_user_required_actions(required_actions: list, user_id: str) -> None:
413441 for required_action in required_actions :
414442 if required_action == "UPDATE_PASSWORD" :
415443 raise ERROR_UPDATE_PASSWORD_REQUIRED (user_id = user_id )
444+
445+ def _increment_issue_attempts (self , domain_id : str , credentials : dict ) -> None :
446+ if cache .is_set ():
447+ ordered_credentials = OrderedDict (sorted (credentials .items ()))
448+ hashed_credentials = utils .dict_to_hash (ordered_credentials )
449+ cache_key = f"identity:token:issue-attempt:{ domain_id } :{ hashed_credentials } "
450+
451+ issue_attempts : int = cache .get (cache_key ) or 0
452+
453+ if issue_attempts == 0 :
454+ cache .set (cache_key , value = 0 , expire = self .ISSUE_BLOCK_TIME )
455+ elif issue_attempts == self .MAX_ISSUE_ATTEMPTS :
456+ cache .set (cache_key , value = issue_attempts , expire = self .ISSUE_BLOCK_TIME )
457+ _LOGGER .debug (f"[_increment_login_attempts] { issue_attempts } attempts" )
458+ elif issue_attempts > self .MAX_ISSUE_ATTEMPTS :
459+ raise ERROR_LOGIN_BLOCKED ()
460+
461+ cache .increment (cache_key )
462+
463+ @staticmethod
464+ def _clear_issue_attempts (domain_id : str , credentials : dict ) -> None :
465+ if cache .is_set ():
466+ ordered_credentials = OrderedDict (sorted (credentials .items ()))
467+ hashed_credentials = utils .dict_to_hash (ordered_credentials )
468+ cache_key = f"identity:token:issue-attempt:{ domain_id } :{ hashed_credentials } "
469+ cache .delete (cache_key )
470+
471+ def _load_conf (self ):
472+ identity_conf = config .get_global ("IDENTITY" ) or {}
473+ token_conf = identity_conf .get ("token" , {})
474+
475+ self .ISSUE_BLOCK_TIME = token_conf .get ("issue_block_time" , 300 )
476+ self .MAX_ISSUE_ATTEMPTS = token_conf .get ("max_issue_attempts" , 10 )
0 commit comments