11# SPDX-FileCopyrightText: 2024 MTS PJSC
22# SPDX-License-Identifier: Apache-2.0
3+ import base64
34import logging
5+ from http import HTTPStatus
46from typing import Annotated , Any
57
6- from fastapi import Depends , FastAPI , Request
8+ from fastapi import Depends , FastAPI , HTTPException , Request
79from keycloak import KeycloakOpenID
10+ from keycloak .exceptions import KeycloakConnectionError
811
912from data_rentgen .db .models import User
1013from data_rentgen .dependencies import Stub
@@ -69,6 +72,7 @@ async def get_token_authorization_code_grant(
6972 redirect_uri = redirect_uri ,
7073 )
7174 except Exception as e :
75+ logger .error ("Error when trying to get token: %s" , e )
7276 raise AuthorizationError ("Failed to get token" ) from e
7377
7478 async def get_current_user (self , access_token : str , * args , ** kwargs ) -> User :
@@ -77,23 +81,22 @@ async def get_current_user(self, access_token: str, *args, **kwargs) -> User:
7781
7882 if not access_token :
7983 logger .debug ("No access token found in session." )
80- self .redirect_to_auth ()
84+ self .redirect_to_auth (str ( request . url ) )
8185
8286 # if user is disabled or blocked in Keycloak after the token is issued, he will
8387 # remain authorized until the token expires (not more than 15 minutes in MTS SSO)
8488 token_info = self .decode_token (access_token )
85-
8689 if token_info is None and refresh_token :
8790 logger .debug ("Access token invalid. Attempting to refresh." )
88- access_token , refresh_token = self .refresh_access_token (refresh_token )
91+ access_token , refresh_token = self .refresh_access_token (refresh_token , str ( request . url ) )
8992 request .session ["access_token" ] = access_token
9093 request .session ["refresh_token" ] = refresh_token
9194
9295 token_info = self .decode_token (access_token )
9396
9497 if token_info is None :
9598 # If there is no token_info after refresh user get redirect
96- self .redirect_to_auth ()
99+ self .redirect_to_auth (str ( request . url ) )
97100
98101 # these names are hardcoded in keycloak:
99102 # https://github.com/keycloak/keycloak/blob/3ca3a4ad349b4d457f6829eaf2ae05f1e01408be/core/src/main/java/org/keycloak/representations/IDToken.java
@@ -103,25 +106,42 @@ async def get_current_user(self, access_token: str, *args, **kwargs) -> User:
103106 raise AuthorizationError ("Invalid token payload" )
104107 return await self ._uow .user .get_or_create (UserDTO (name = login )) # type: ignore[arg-type]
105108
109+ async def logout (self , refresh_token : str ):
110+ try :
111+ return self .keycloak_openid .logout (refresh_token )
112+ except KeycloakConnectionError as err :
113+ logger .error ("Error when trying to get token: %s" , err )
114+ return None
115+
106116 def decode_token (self , access_token : str ) -> dict [str , Any ] | None :
107117 try :
108118 return self .keycloak_openid .decode_token (token = access_token )
109119 except Exception as err :
110120 logger .info ("Access token is invalid or expired: %s" , err )
111121 return None
112122
113- def refresh_access_token (self , refresh_token : str ) -> tuple [str , str ]: # type: ignore[return]
123+ def refresh_access_token (self , refresh_token : str , origin_url : str ) -> tuple [str , str ]: # type: ignore[return]
114124 try :
115125 new_tokens = self .keycloak_openid .refresh_token (refresh_token )
116126 logger .debug ("Access token refreshed" )
117127 return new_tokens .get ("access_token" ), new_tokens .get ("refresh_token" )
118128 except Exception as err :
119129 logger .debug ("Failed to refresh access token: %s" , err )
120- self .redirect_to_auth ()
130+ self .redirect_to_auth (origin_url )
121131
122- def redirect_to_auth (self ) -> None :
123- auth_url = self .keycloak_openid .auth_url (
124- redirect_uri = self .settings .keycloak .redirect_uri ,
125- scope = self .settings .keycloak .scope ,
126- )
127- raise RedirectError (message = auth_url , details = "Authorize on provided url" )
132+ def redirect_to_auth (self , state : str = "" ):
133+ try :
134+ state = base64 .b64encode (state .encode ("utf-8" )) # type: ignore[assignment]
135+
136+ auth_url = self .keycloak_openid .auth_url (
137+ redirect_uri = self .settings .keycloak .redirect_uri ,
138+ scope = self .settings .keycloak .scope ,
139+ state = state .decode ("utf-8" ), # type: ignore[attr-defined]
140+ )
141+
142+ except KeycloakConnectionError as err :
143+ logger .error ("Failed connect to Keycloak: %s" , err )
144+ # TODO: What exception should we raise here?
145+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST , detail = err )
146+ logger .info ("Raising redirect error with url: %s" , auth_url )
147+ raise RedirectError (message = "Authorize on provided url" , details = auth_url )
0 commit comments