diff --git a/apps/authentication/backends/oidc/__init__.py b/apps/authentication/backends/oidc/__init__.py index e69de29bb..a82161b8e 100644 --- a/apps/authentication/backends/oidc/__init__.py +++ b/apps/authentication/backends/oidc/__init__.py @@ -0,0 +1,4 @@ +""" +使用下面的工程,进行jumpserver 的 oidc 认证 +https://github.com/BaiJiangJie/jumpserver-django-oidc-rp +""" \ No newline at end of file diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py deleted file mode 100644 index 2fa4a2555..000000000 --- a/apps/authentication/backends/oidc/backends.py +++ /dev/null @@ -1,181 +0,0 @@ -import requests -from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend -from django.core.exceptions import SuspiciousOperation -from django.conf import settings -from django.db import transaction -from django.urls import reverse -from django.utils.module_loading import import_string - -from oidc_rp.conf import settings as oidc_rp_settings -from oidc_rp.models import OIDCUser -from oidc_rp.signals import oidc_user_created -from oidc_rp.backends import OIDCAuthBackend -from oidc_rp.utils import validate_and_return_id_token - - -__all__ = ['OIDCAuthCodeBackend', 'OIDCAuthPasswordBackend'] - - -class OIDCAuthCodeBackend(OIDCAuthBackend): - def authenticate(self, request, nonce=None, **kwargs): - """ Authenticates users in case of the OpenID Connect Authorization code flow. """ - # NOTE: the request object is mandatory to perform the authentication using an authorization - # code provided by the OIDC supplier. - if (nonce is None and oidc_rp_settings.USE_NONCE) or request is None: - return - - # Fetches required GET parameters from the HTTP request object. - state = request.GET.get('state') - code = request.GET.get('code') - - # Don't go further if the state value or the authorization code is not present in the GET - # parameters because we won't be able to get a valid token for the user in that case. - if (state is None and oidc_rp_settings.USE_STATE) or code is None: - raise SuspiciousOperation('Authorization code or state value is missing') - - # Prepares the token payload that will be used to request an authentication token to the - # token endpoint of the OIDC provider. - token_payload = { - 'client_id': oidc_rp_settings.CLIENT_ID, - 'client_secret': oidc_rp_settings.CLIENT_SECRET, - 'grant_type': 'authorization_code', - 'code': code, - 'redirect_uri': request.build_absolute_uri( - reverse(settings.OIDC_RP_LOGIN_CALLBACK_URL_NAME) - ), - } - - # Calls the token endpoint. - token_response = requests.post(oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, data=token_payload) - token_response.raise_for_status() - token_response_data = token_response.json() - - # Validates the token. - raw_id_token = token_response_data.get('id_token') - id_token = validate_and_return_id_token(raw_id_token, nonce) - if id_token is None: - 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 - - # If the id_token contains userinfo scopes and claims we don't have to hit the userinfo - # endpoint. - if oidc_rp_settings.ID_TOKEN_INCLUDE_USERINFO: - userinfo_data = id_token - else: - # Fetches the user information from the userinfo endpoint provided by the OP. - userinfo_response = requests.get( - oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, - headers={'Authorization': 'Bearer {0}'.format(access_token)}) - userinfo_response.raise_for_status() - userinfo_data = userinfo_response.json() - - # Tries to retrieve a corresponding user in the local database and creates it if applicable. - try: - oidc_user = OIDCUser.objects.select_related('user').get(sub=userinfo_data.get('sub')) - except OIDCUser.DoesNotExist: - oidc_user = create_oidc_user_from_claims(userinfo_data) - oidc_user_created.send(sender=self.__class__, request=request, oidc_user=oidc_user) - else: - update_oidc_user_from_claims(oidc_user, userinfo_data) - - # Runs a custom user details handler if applicable. Such handler could be responsible for - # creating / updating whatever is necessary to manage the considered user (eg. a profile). - user_details_handler(oidc_user, userinfo_data) - - return oidc_user.user - - -class OIDCAuthPasswordBackend(ModelBackend): - - def authenticate(self, request, username=None, password=None, **kwargs): - - if username is None and password is None: - return - - # Prepares the token payload that will be used to request an authentication token to the - # token endpoint of the OIDC provider. - token_payload = { - 'client_id': oidc_rp_settings.CLIENT_ID, - 'client_secret': oidc_rp_settings.CLIENT_SECRET, - 'grant_type': 'password', - 'username': username, - 'password': password, - } - - token_response = requests.post(oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, data=token_payload) - token_response.raise_for_status() - token_response_data = token_response.json() - - access_token = token_response_data.get('access_token') - - # Fetches the user information from the userinfo endpoint provided by the OP. - userinfo_response = requests.get( - oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, - headers={'Authorization': 'Bearer {0}'.format(access_token)}) - userinfo_response.raise_for_status() - userinfo_data = userinfo_response.json() - - # Tries to retrieve a corresponding user in the local database and creates it if applicable. - try: - oidc_user = OIDCUser.objects.select_related('user').get(sub=userinfo_data.get('sub')) - except OIDCUser.DoesNotExist: - oidc_user = create_oidc_user_from_claims(userinfo_data) - oidc_user_created.send(sender=self.__class__, request=request, oidc_user=oidc_user) - else: - update_oidc_user_from_claims(oidc_user, userinfo_data) - - # Runs a custom user details handler if applicable. Such handler could be responsible for - # creating / updating whatever is necessary to manage the considered user (eg. a profile). - user_details_handler(oidc_user, userinfo_data) - - return oidc_user.user - - -def get_or_create_user(username, email): - user, created = get_user_model().objects.get_or_create(username=username) - return user - - -@transaction.atomic -def create_oidc_user_from_claims(claims): - """ - Creates an ``OIDCUser`` instance using the claims extracted - from an id_token. - """ - sub = claims['sub'] - email = claims.get('email') - username = claims.get('preferred_username') - user = get_or_create_user(username, email) - oidc_user = OIDCUser.objects.create(user=user, sub=sub, userinfo=claims) - - return oidc_user - - -@transaction.atomic -def update_oidc_user_from_claims(oidc_user, claims): - """ - Updates an ``OIDCUser`` instance using the claims extracted - from an id_token. - """ - oidc_user.userinfo = claims - oidc_user.save() - - -@transaction.atomic -def user_details_handler(oidc_user, userinfo_data): - name = userinfo_data.get('name') - username = userinfo_data.get('preferred_username') - email = userinfo_data.get('email') - oidc_user.user.name = name or username - oidc_user.user.username = username - oidc_user.user.email = email - oidc_user.user.save() diff --git a/apps/authentication/backends/oidc/urls.py b/apps/authentication/backends/oidc/urls.py deleted file mode 100644 index 173269d0d..000000000 --- a/apps/authentication/backends/oidc/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from oidc_rp import views as oidc_rp_views -from .views import OverwriteOIDCAuthRequestView, OverwriteOIDCEndSessionView - - -urlpatterns = [ - path('login/', OverwriteOIDCAuthRequestView.as_view(), name='oidc-login'), - path('callback/', oidc_rp_views.OIDCAuthCallbackView.as_view(), name='oidc-callback'), - path('logout/', OverwriteOIDCEndSessionView.as_view(), name='oidc-logout'), -] diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py deleted file mode 100644 index 4db0c4138..000000000 --- a/apps/authentication/backends/oidc/views.py +++ /dev/null @@ -1,77 +0,0 @@ -from django.conf import settings -from django.http import HttpResponseRedirect, QueryDict -from django.urls import reverse -from django.utils.crypto import get_random_string -from django.utils.http import is_safe_url, urlencode - -from oidc_rp.conf import settings as oidc_rp_settings -from oidc_rp.views import OIDCEndSessionView, OIDCAuthRequestView - -__all__ = ['OverwriteOIDCAuthRequestView', 'OverwriteOIDCEndSessionView'] - - -class OverwriteOIDCAuthRequestView(OIDCAuthRequestView): - def get(self, request): - """ Processes GET requests. """ - # Defines common parameters used to bootstrap the authentication request. - authentication_request_params = request.GET.dict() - authentication_request_params.update({ - 'scope': oidc_rp_settings.SCOPES, - 'response_type': 'code', - 'client_id': oidc_rp_settings.CLIENT_ID, - 'redirect_uri': request.build_absolute_uri( - reverse(settings.OIDC_RP_LOGIN_CALLBACK_URL_NAME) - ), - }) - - # States should be used! They are recommended in order to maintain state between the - # authentication request and the callback. - if oidc_rp_settings.USE_STATE: - state = get_random_string(oidc_rp_settings.STATE_LENGTH) - authentication_request_params.update({'state': state}) - request.session['oidc_auth_state'] = state - - # Nonces should be used too! In that case the generated nonce is stored both in the - # authentication request parameters and in the user's session. - if oidc_rp_settings.USE_NONCE: - nonce = get_random_string(oidc_rp_settings.NONCE_LENGTH) - authentication_request_params.update({'nonce': nonce, }) - request.session['oidc_auth_nonce'] = nonce - - # Stores the "next" URL in the session if applicable. - next_url = request.GET.get('next') - request.session['oidc_auth_next_url'] = next_url \ - if is_safe_url(url=next_url, allowed_hosts=(request.get_host(), )) else None - - # Redirects the user to authorization endpoint. - query = urlencode(authentication_request_params) - redirect_url = '{url}?{query}'.format( - url=oidc_rp_settings.PROVIDER_AUTHORIZATION_ENDPOINT, query=query) - return HttpResponseRedirect(redirect_url) - - -class OverwriteOIDCEndSessionView(OIDCEndSessionView): - def post(self, request): - """ Processes POST requests. """ - logout_url = settings.LOGOUT_REDIRECT_URL or '/' - - try: - logout_url = self.provider_end_session_url \ - if oidc_rp_settings.PROVIDER_END_SESSION_ENDPOINT else logout_url - except KeyError: # pragma: no cover - logout_url = logout_url - - # Redirects the user to the appropriate URL. - return HttpResponseRedirect(logout_url) - - @property - def provider_end_session_url(self): - """ Returns the end-session URL. """ - q = QueryDict(mutable=True) - q[oidc_rp_settings.PROVIDER_END_SESSION_REDIRECT_URI_PARAMETER] = \ - self.request.build_absolute_uri(settings.LOGOUT_REDIRECT_URL or '/') - if self.request.session.get('oidc_auth_id_token'): - q[oidc_rp_settings.PROVIDER_END_SESSION_ID_TOKEN_PARAMETER] = \ - self.request.session['oidc_auth_id_token'] - return '{}?{}'.format(oidc_rp_settings.PROVIDER_END_SESSION_ENDPOINT, q.urlencode()) - diff --git a/apps/authentication/backends/openid/__init__.py b/apps/authentication/backends/openid/__init__.py deleted file mode 100644 index 9ed3bea78..000000000 --- a/apps/authentication/backends/openid/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from .backends import * -from .middleware import * -from .utils import * -from .decorator import * diff --git a/apps/authentication/backends/openid/backends.py b/apps/authentication/backends/openid/backends.py deleted file mode 100644 index 938566e2a..000000000 --- a/apps/authentication/backends/openid/backends.py +++ /dev/null @@ -1,82 +0,0 @@ -# coding:utf-8 -# - -from django.contrib.auth import get_user_model -from django.conf import settings - -from common.utils import get_logger -from .utils import new_client -from .models import OIDT_ACCESS_TOKEN - -UserModel = get_user_model() - -logger = get_logger(__file__) -client = new_client() - - -__all__ = [ - 'OpenIDAuthorizationCodeBackend', 'OpenIDAuthorizationPasswordBackend', -] - - -class BaseOpenIDAuthorizationBackend(object): - @staticmethod - def user_can_authenticate(user): - """ - Reject users with is_active=False. Custom user models that don't have - that attribute are allowed. - """ - is_valid = getattr(user, 'is_valid', None) - return is_valid or is_valid is None - - def get_user(self, user_id): - try: - user = UserModel._default_manager.get(pk=user_id) - except UserModel.DoesNotExist: - return None - - return user if self.user_can_authenticate(user) else None - - -class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): - def authenticate(self, request, **kwargs): - logger.info('Authentication OpenID code backend') - code = kwargs.get('code') - redirect_uri = kwargs.get('redirect_uri') - if not code or not redirect_uri: - logger.info('Authenticate failed: No code or No redirect uri') - return None - try: - oidt_profile = client.update_or_create_from_code( - code=code, redirect_uri=redirect_uri - ) - except Exception as e: - logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) - return None - else: - # Check openid user single logout or not with access_token - request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token - user = oidt_profile.user - logger.info('Authenticate success: user -> {}'.format(user)) - return user if self.user_can_authenticate(user) else None - - -class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend): - def authenticate(self, request, username=None, password=None, **kwargs): - logger.info('Authentication OpenID password backend') - if not username: - logger.info('Authenticate failed: Not username') - return None - try: - oidt_profile = client.update_or_create_from_password( - username=username, password=password - ) - except Exception as e: - logger.error(e, exc_info=True) - logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) - return None - else: - user = oidt_profile.user - logger.info('Authenticate success: user -> {}'.format(user)) - return user if self.user_can_authenticate(user) else None - diff --git a/apps/authentication/backends/openid/decorator.py b/apps/authentication/backends/openid/decorator.py deleted file mode 100644 index 7286b7a2f..000000000 --- a/apps/authentication/backends/openid/decorator.py +++ /dev/null @@ -1,57 +0,0 @@ -# coding: utf-8 -# - -import warnings -import contextlib - -import requests -from urllib3.exceptions import InsecureRequestWarning -from django.conf import settings - -__all__ = [ - 'ssl_verification', -] - -old_merge_environment_settings = requests.Session.merge_environment_settings - - -@contextlib.contextmanager -def no_ssl_verification(): - """ - https://stackoverflow.com/questions/15445981/ - how-do-i-disable-the-security-certificate-check-in-python-requests - """ - opened_adapters = set() - - def merge_environment_settings(self, url, proxies, stream, verify, cert): - # Verification happens only once per connection so we need to close - # all the opened adapters once we're done. Otherwise, the effects of - # verify=False persist beyond the end of this context manager. - opened_adapters.add(self.get_adapter(url)) - _settings = old_merge_environment_settings( - self, url, proxies, stream, verify, cert - ) - _settings['verify'] = False - return _settings - - requests.Session.merge_environment_settings = merge_environment_settings - try: - with warnings.catch_warnings(): - warnings.simplefilter('ignore', InsecureRequestWarning) - yield - finally: - requests.Session.merge_environment_settings = old_merge_environment_settings - for adapter in opened_adapters: - try: - adapter.close() - except: - pass - - -def ssl_verification(func): - def wrapper(*args, **kwargs): - if not settings.AUTH_OPENID_IGNORE_SSL_VERIFICATION: - return func(*args, **kwargs) - with no_ssl_verification(): - return func(*args, **kwargs) - return wrapper diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py deleted file mode 100644 index bacb4858c..000000000 --- a/apps/authentication/backends/openid/middleware.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding:utf-8 -# - -from django.conf import settings -from django.contrib.auth import logout -from django.utils.deprecation import MiddlewareMixin -from django.contrib.auth import BACKEND_SESSION_KEY - -from common.utils import get_logger -from .utils import new_client -from .models import OIDT_ACCESS_TOKEN - -BACKEND_OPENID_AUTH_CODE = 'OpenIDAuthorizationCodeBackend' -logger = get_logger(__file__) -__all__ = ['OpenIDAuthenticationMiddleware'] - - -class OpenIDAuthenticationMiddleware(MiddlewareMixin): - """ - Check openid user single logout (with access_token) - """ - def process_request(self, request): - # Don't need openid auth if AUTH_OPENID is False - if not settings.AUTH_OPENID: - return - # Don't need openid auth if no shared session enabled - if not settings.AUTH_OPENID_SHARE_SESSION: - return - # Don't need check single logout if user not authenticated - if not request.user.is_authenticated: - return - elif not request.session[BACKEND_SESSION_KEY].endswith( - BACKEND_OPENID_AUTH_CODE): - return - # Check openid user single logout or not with access_token - try: - client = new_client() - client.get_userinfo(token=request.session.get(OIDT_ACCESS_TOKEN)) - except Exception as e: - logout(request) - logger.error(e) diff --git a/apps/authentication/backends/openid/models.py b/apps/authentication/backends/openid/models.py deleted file mode 100644 index a945e8eb3..000000000 --- a/apps/authentication/backends/openid/models.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import transaction -from django.contrib.auth import get_user_model -from keycloak.realm import KeycloakRealm -from keycloak.keycloak_openid import KeycloakOpenID -from users.utils import construct_user_email - -from .signals import post_create_or_update_openid_user -from .decorator import ssl_verification - -OIDT_ACCESS_TOKEN = 'oidt_access_token' - - -class Nonce(object): - """ - The openid-login is stored in cache as a temporary object, recording the - user's redirect_uri and next_pat - """ - def __init__(self, redirect_uri, next_path): - import uuid - self.state = uuid.uuid4() - self.redirect_uri = redirect_uri - self.next_path = next_path - - -class OpenIDTokenProfile(object): - def __init__(self, user, access_token, refresh_token): - """ - :param user: User object - :param access_token: - :param refresh_token: - """ - self.user = user - self.access_token = access_token - self.refresh_token = refresh_token - - def __str__(self): - return "{}'s OpenID token profile".format(self.user.username) - - -class Client(object): - def __init__(self, server_url, realm_name, client_id, client_secret): - self.server_url = server_url - self.realm_name = realm_name - self.client_id = client_id - self.client_secret = client_secret - self._openid_client = None - self._realm = None - self._openid_connect_client = None - - @property - def realm(self): - if self._realm is None: - self._realm = KeycloakRealm( - server_url=self.server_url, - realm_name=self.realm_name, - headers={} - ) - return self._realm - - @property - def openid_connect_client(self): - """ - :rtype: keycloak.openid_connect.KeycloakOpenidConnect - """ - if self._openid_connect_client is None: - self._openid_connect_client = self.realm.open_id_connect( - client_id=self.client_id, - client_secret=self.client_secret - ) - return self._openid_connect_client - - @property - def openid_client(self): - """ - :rtype: keycloak.keycloak_openid.KeycloakOpenID - """ - if self._openid_client is None: - self._openid_client = KeycloakOpenID( - server_url='%sauth/' % self.server_url, - realm_name=self.realm_name, - client_id=self.client_id, - client_secret_key=self.client_secret, - ) - return self._openid_client - - @ssl_verification - def get_url(self, name): - return self.openid_connect_client.get_url(name=name) - - def get_url_end_session_endpoint(self): - return self.get_url(name='end_session_endpoint') - - @ssl_verification - def get_authorization_url(self, redirect_uri, scope, state): - url = self.openid_connect_client.authorization_url( - redirect_uri=redirect_uri, scope=scope, state=state - ) - return url - - @ssl_verification - def get_userinfo(self, token): - user_info = self.openid_connect_client.userinfo(token=token) - return user_info - - @ssl_verification - def authorization_code(self, code, redirect_uri): - token_response = self.openid_connect_client.authorization_code( - code=code, redirect_uri=redirect_uri - ) - return token_response - - @ssl_verification - def authorization_password(self, username, password): - token_response = self.openid_client.token( - username=username, password=password - ) - return token_response - - def update_or_create_from_code(self, code, redirect_uri): - """ - Update or create an user based on an authentication code. - Response as specified in: - https://tools.ietf.org/html/rfc6749#section-4.1.4 - :param str code: authentication code - :param str redirect_uri: - :rtype: OpenIDTokenProfile - """ - token_response = self.authorization_code(code, redirect_uri) - return self._update_or_create(token_response=token_response) - - def update_or_create_from_password(self, username, password): - """ - Update or create an user based on an authentication username and password. - :param str username: authentication username - :param str password: authentication password - :return: OpenIDTokenProfile - """ - token_response = self.authorization_password(username, password) - return self._update_or_create(token_response=token_response) - - def _update_or_create(self, token_response): - """ - Update or create an user based on a token response. - `token_response` contains the items returned by the OpenIDConnect Token API - end-point: - - id_token - - access_token - - expires_in - - refresh_token - - refresh_expires_in - :param dict token_response: - :rtype: OpenIDTokenProfile - """ - userinfo = self.get_userinfo(token=token_response['access_token']) - with transaction.atomic(): - name = userinfo.get('name', '') - username = userinfo.get('preferred_username', '') - email = userinfo.get('email', '') - email = construct_user_email(username, email) - - user, created = get_user_model().objects.update_or_create( - username=username, - defaults={ - 'name': name, 'email': email, - 'first_name': userinfo.get('given_name', ''), - 'last_name': userinfo.get('family_name', ''), - } - ) - oidt_profile = OpenIDTokenProfile( - user=user, - access_token=token_response['access_token'], - refresh_token=token_response['refresh_token'], - ) - if user: - post_create_or_update_openid_user.send( - sender=user.__class__, user=user, created=created - ) - - return oidt_profile - - def __str__(self): - return self.client_id diff --git a/apps/authentication/backends/openid/signals.py b/apps/authentication/backends/openid/signals.py deleted file mode 100644 index ad81bca4a..000000000 --- a/apps/authentication/backends/openid/signals.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.dispatch import Signal - - -post_create_or_update_openid_user = Signal(providing_args=('user',)) -post_openid_login_success = Signal(providing_args=('user', 'request')) diff --git a/apps/authentication/backends/openid/tests.py b/apps/authentication/backends/openid/tests.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/authentication/backends/openid/urls.py b/apps/authentication/backends/openid/urls.py deleted file mode 100644 index 019529e12..000000000 --- a/apps/authentication/backends/openid/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.urls import path - -from . import views - -urlpatterns = [ - path('login/', views.OpenIDLoginView.as_view(), name='openid-login'), - path('login/complete/', views.OpenIDLoginCompleteView.as_view(), - name='openid-login-complete'), -] diff --git a/apps/authentication/backends/openid/utils.py b/apps/authentication/backends/openid/utils.py deleted file mode 100644 index 15160d224..000000000 --- a/apps/authentication/backends/openid/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.conf import settings -from .models import Client - -__all__ = ['new_client'] - - -def new_client(): - """ - :return: authentication.models.Client - """ - return Client( - server_url=settings.AUTH_OPENID_SERVER_URL, - realm_name=settings.AUTH_OPENID_REALM_NAME, - client_id=settings.AUTH_OPENID_CLIENT_ID, - client_secret=settings.AUTH_OPENID_CLIENT_SECRET - ) diff --git a/apps/authentication/backends/openid/views.py b/apps/authentication/backends/openid/views.py deleted file mode 100644 index 89c935452..000000000 --- a/apps/authentication/backends/openid/views.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import logging - -from django.conf import settings -from django.core.cache import cache -from django.views.generic.base import RedirectView -from django.contrib.auth import authenticate, login -from django.http.response import ( - HttpResponseBadRequest, - HttpResponseServerError, - HttpResponseRedirect -) - -from .utils import new_client -from .models import Nonce -from .signals import post_openid_login_success - -logger = logging.getLogger(__name__) -client = new_client() - -__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView'] - - -class OpenIDLoginView(RedirectView): - def get_redirect_url(self, *args, **kwargs): - redirect_uri = settings.BASE_SITE_URL + \ - str(settings.AUTH_OPENID_LOGIN_COMPLETE_URL) - nonce = Nonce( - redirect_uri=redirect_uri, - next_path=self.request.GET.get('next') - ) - cache.set(str(nonce.state), nonce, 24*3600) - - self.request.session['openid_state'] = str(nonce.state) - authorization_url = client.get_authorization_url( - redirect_uri=nonce.redirect_uri, - scope='code', - state=str(nonce.state) - ) - return authorization_url - - -class OpenIDLoginCompleteView(RedirectView): - def get(self, request, *args, **kwargs): - if 'error' in request.GET: - return HttpResponseServerError(self.request.GET['error']) - if 'code' not in self.request.GET and 'state' not in self.request.GET: - return HttpResponseBadRequest(content='Code or State is empty') - if self.request.GET['state'] != self.request.session['openid_state']: - return HttpResponseBadRequest(content='State invalid') - nonce = cache.get(self.request.GET['state']) - if not nonce: - return HttpResponseBadRequest(content='State failure') - - user = authenticate( - request=self.request, - code=self.request.GET['code'], - redirect_uri=nonce.redirect_uri - ) - cache.delete(str(nonce.state)) - if not user: - return HttpResponseBadRequest(content='Authenticate user failed') - - login(self.request, user) - post_openid_login_success.send( - sender=self.__class__, user=user, request=self.request - ) - return HttpResponseRedirect(nonce.next_path or '/') - diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index 23323b15a..d782a05fc 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -171,7 +171,7 @@ class MFARequiredError(NeedMoreInfoError): 'error': self.error, 'msg': self.msg, 'data': { - 'choices': ['otp'], + 'choices': ['code'], 'url': reverse('api-auth:mfa-challenge') } } diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 1c4fb5aa1..5b3738c98 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -62,8 +62,7 @@ class AuthMixin: password = request.POST.get('password', '') public_key = request.POST.get('public_key', '') user, error = check_user_valid( - username=username, password=password, - public_key=public_key + request=request, username=username, password=password, public_key=public_key ) ip = self.get_request_ip() if not user: diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signals_handlers.py index 2d73ae9b7..645b202c2 100644 --- a/apps/authentication/signals_handlers.py +++ b/apps/authentication/signals_handlers.py @@ -1,55 +1,15 @@ -from django.http.request import QueryDict -from django.conf import settings from django.dispatch import receiver -from django.contrib.auth.signals import user_logged_out -from django_auth_ldap.backend import populate_user -from oidc_rp.signals import oidc_user_created -from users.models import User -from .backends.openid import new_client -from .backends.openid.signals import ( - post_create_or_update_openid_user, post_openid_login_success -) -from .signals import post_auth_success +from jms_oidc_rp.signals import oidc_user_login_success, oidc_user_login_failed + +from .signals import post_auth_success, post_auth_failed -@receiver(user_logged_out) -def on_user_logged_out(sender, request, user, **kwargs): - query = QueryDict('', mutable=True) - query.update({ - 'redirect_uri': settings.BASE_SITE_URL - }) - # openid (keycloak) - if settings.AUTH_OPENID and settings.AUTH_OPENID_SHARE_SESSION: - client = new_client() - end_session_endpoint = client.get_url_end_session_endpoint() - openid_logout_url = "%s?%s" % (end_session_endpoint, query.urlencode()) - request.COOKIES['next'] = openid_logout_url - return +@receiver(oidc_user_login_success) +def on_oidc_user_login_success(sender, request, user, **kwargs): + post_auth_success.send(sender, user=user, request=request) -@receiver(post_create_or_update_openid_user) -def on_post_create_or_update_openid_user(sender, user=None, created=True, **kwargs): - if created and user and user.username != 'admin': - user.source = user.SOURCE_OPENID - user.save() - - -@receiver(post_openid_login_success) -def on_openid_login_success(sender, user=None, request=None, **kwargs): - post_auth_success.send(sender=sender, user=user, request=request) - - -@receiver(populate_user) -def on_ldap_create_user(sender, user, ldap_user, **kwargs): - if user and user.username not in ['admin']: - exists = User.objects.filter(username=user.username).exists() - if not exists: - user.source = user.SOURCE_LDAP - user.save() - - -@receiver(oidc_user_created) -def on_oidc_user_created(sender, request, oidc_user, **kwargs): - oidc_user.user.source = User.SOURCE_OPENID - oidc_user.user.save() +@receiver(oidc_user_login_failed) +def on_oidc_user_login_failed(sender, username, request, reason, **kwargs): + post_auth_failed.send(sender, username=username, request=request, reason=reason) diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 21d775633..1f842d7a2 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -52,21 +52,14 @@ - {% if AUTH_OPENID or AUTH_OIDC_RP %} + {% if AUTH_OPENID %}
{% trans "More login options" %}