diff --git a/apps/authentication/backends/oauth2/backends.py b/apps/authentication/backends/oauth2/backends.py index 32e3e67bb..4f352696c 100644 --- a/apps/authentication/backends/oauth2/backends.py +++ b/apps/authentication/backends/oauth2/backends.py @@ -69,13 +69,25 @@ class OAuth2Backend(RedirectAuthBackend): response_data = response_data['data'] return response_data - def authenticate(self, request, code=None): + def authenticate(self, request, code=None, state=None): log_prompt = "Process authenticate [OAuth2Backend]: {}" logger.debug(log_prompt.format('Start')) if code is None: logger.error(log_prompt.format('code is missing')) return None + if settings.AUTH_OAUTH2_USE_STATE: + if state is None: + logger.error(log_prompt.format('state is missing')) + return None + + session_state = request.session.get('oauth2_state') + if not session_state or session_state != state: + logger.error(log_prompt.format('state parameter mismatch')) + return None + + request.session.pop('oauth2_state', None) + query_dict = { 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': build_absolute_uri( diff --git a/apps/authentication/backends/oauth2/views.py b/apps/authentication/backends/oauth2/views.py index 22dfc15e3..7b3c0a26b 100644 --- a/apps/authentication/backends/oauth2/views.py +++ b/apps/authentication/backends/oauth2/views.py @@ -1,3 +1,4 @@ +import uuid from django.conf import settings from django.contrib import auth from django.http import HttpResponseRedirect @@ -37,6 +38,11 @@ class OAuth2AuthRequestView(View): 'redirect_uri': redirect_uri } + if settings.AUTH_OAUTH2_USE_STATE: + state = uuid.uuid4() + request.session['oauth2_state'] = state + query_dict['state'] = state + if '?' in settings.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT: separator = '&' else: @@ -60,9 +66,19 @@ class OAuth2AuthCallbackView(View, FlashMessageMixin): logger.debug(log_prompt.format('Start')) callback_params = request.GET + state = None + if settings.AUTH_OAUTH2_USE_STATE: + state = callback_params.get('state') + session_state = request.session.get('oauth2_state') + if not state or not session_state or session_state != state: + logger.error("Invalid state parameter") + response = self.get_failed_response('/', title=_('OAuth2 Error'), msg="Invalid state parameter") + return response + request.session.pop('oauth2_state', None) + if 'code' in callback_params: logger.debug(log_prompt.format('Process authenticate')) - user = authenticate(code=callback_params['code'], request=request) + user = authenticate(code=callback_params['code'], request=request, state=state) if user: logger.debug(log_prompt.format('Login: {}'.format(user))) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 26ca5a6bd..61051c8ba 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -446,6 +446,7 @@ class Config(dict): 'AUTH_OAUTH2': False, 'AUTH_OAUTH2_LOGO_PATH': 'img/login_oauth2_logo.png', 'AUTH_OAUTH2_PROVIDER': 'OAuth2', + 'AUTH_OAUTH2_USE_STATE': False, 'AUTH_OAUTH2_ALWAYS_UPDATE_USER': True, 'AUTH_OAUTH2_CLIENT_ID': 'client-id', 'AUTH_OAUTH2_SCOPE': '', diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 8e75d3291..6783de090 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -203,6 +203,7 @@ SAML2_LOGOUT_URL_NAME = "authentication:saml2:saml2-logout" AUTH_OAUTH2 = CONFIG.AUTH_OAUTH2 AUTH_OAUTH2_LOGO_PATH = CONFIG.AUTH_OAUTH2_LOGO_PATH AUTH_OAUTH2_PROVIDER = CONFIG.AUTH_OAUTH2_PROVIDER +AUTH_OAUTH2_USE_STATE = CONFIG.AUTH_OAUTH2_USE_STATE AUTH_OAUTH2_ALWAYS_UPDATE_USER = CONFIG.AUTH_OAUTH2_ALWAYS_UPDATE_USER AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT = CONFIG.AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT = CONFIG.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT