import time import requests import requests.exceptions from django.conf import settings from django.contrib import auth from django.core.exceptions import MiddlewareNotUsed from common.utils import get_logger from .decorator import ssl_verification from .utils import validate_and_return_id_token logger = get_logger(__file__) class OIDCRefreshIDTokenMiddleware: """ Allows to periodically refresh the ID token associated with the authenticated user. """ def __init__(self, get_response): if not settings.AUTH_OPENID: raise MiddlewareNotUsed self.get_response = get_response def __call__(self, request): # Refreshes tokens only in the applicable cases. if request.method == 'GET' and not self.is_ajax(request) and \ request.user.is_authenticated and settings.AUTH_OPENID: self.refresh_token(request) response = self.get_response(request) return response @staticmethod def is_ajax(request): return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' @ssl_verification def refresh_token(self, request): """ Refreshes the token of the current user. """ log_prompt = "Process refresh Token: {}" # logger.debug(log_prompt.format('Start')) # NOTE: SHARE_SESSION is False means that the user does not share sessions # with other applications if not settings.AUTH_OPENID_SHARE_SESSION: logger.debug(log_prompt.format('Not share session')) return # NOTE: no refresh token in the session means that the user wasn't authentified using the # OpenID Connect provider (OP). refresh_token = request.session.get('oidc_auth_refresh_token') if refresh_token is None: logger.debug(log_prompt.format('Refresh token is missing')) return id_token_exp_timestamp = request.session.get('oidc_auth_id_token_exp_timestamp', None) now_timestamp = time.time() # Returns immediately if the token is still valid. if id_token_exp_timestamp is not None and id_token_exp_timestamp > now_timestamp: # logger.debug(log_prompt.format('Returns immediately because token is still valid')) return # Prepares the token payload that will be used to request a new token from the token # endpoint. refresh_token = request.session.pop('oidc_auth_refresh_token') token_payload = { 'client_id': settings.AUTH_OPENID_CLIENT_ID, 'client_secret': settings.AUTH_OPENID_CLIENT_SECRET, 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'scope': settings.AUTH_OPENID_SCOPES, } # Calls the token endpoint. logger.debug(log_prompt.format('Calls the token endpoint')) token_response = requests.post(settings.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT, data=token_payload) try: token_response.raise_for_status() except requests.exceptions.HTTPError as e: logger.debug(log_prompt.format('Request exception http error: {}'.format(str(e)))) logger.debug(log_prompt.format('Logout')) auth.logout(request) return token_response_data = token_response.json() # Validates the token. logger.debug(log_prompt.format('Validate ID Token')) raw_id_token = token_response_data.get('id_token') id_token = validate_and_return_id_token(raw_id_token, validate_nonce=False) # If the token cannot be validated we have to log out the current user. if id_token is None: logger.debug(log_prompt.format('ID Token is None')) auth.logout(request) logger.debug(log_prompt.format('Logout')) return # Retrieves the access token and refresh token. access_token = token_response_data.get('access_token') refresh_token = token_response_data.get('refresh_token') # Stores the ID token, the related access token and the refresh token in the session. request.session['oidc_auth_id_token'] = raw_id_token request.session['oidc_auth_access_token'] = access_token request.session['oidc_auth_refresh_token'] = refresh_token # Saves the new expiration timestamp. request.session['oidc_auth_id_token_exp_timestamp'] = \ time.time() + settings.AUTH_OPENID_ID_TOKEN_MAX_AGE