diff --git a/irodsbackend/urls.py b/irodsbackend/urls.py index ffa734df4..fdfdf0c6b 100644 --- a/irodsbackend/urls.py +++ b/irodsbackend/urls.py @@ -19,7 +19,7 @@ # NOTE: Not exactly REST API view, but URL maintained for backwards comp path( route='api/auth', - view=views.BasicAuthView.as_view(), + view=views.BasicAuthAPIView.as_view(), name='api_auth', ), ] diff --git a/irodsbackend/views.py b/irodsbackend/views.py index c9fa10de3..60963f107 100644 --- a/irodsbackend/views.py +++ b/irodsbackend/views.py @@ -1,16 +1,13 @@ """Views for the irodsbackend app""" -import base64 import logging from django.conf import settings -from django.contrib.auth import authenticate -from django.http import HttpResponse, JsonResponse -from django.views.generic import View - +from django.http import JsonResponse from rest_framework.response import Response +from rest_framework.views import APIView -from knox.auth import TokenAuthentication +from sodar.users.auth import fallback_to_auth_basic # Projectroles dependency from projectroles.models import SODAR_CONSTANTS @@ -232,56 +229,39 @@ def get(self, request, *args, **kwargs): # Basic Auth View -------------------------------------------------------------- -class BasicAuthView(View): +# NOTE: Must be based on APIView to avoid CSRF token check +@fallback_to_auth_basic +class BasicAuthAPIView(APIView): """ - View for verifying login credentials for OIDC and local users in iRODS. Does - not log in the user. + REST API view for verifying login credentials for local users in iRODS. - To be used in environments enabling OIDC access and/or where external - LDAP/AD login is not available. + Should only be used in local development and testing situations or when an + external LDAP/AD login is not available. """ http_method_names = ['post'] def post(self, request, *args, **kwargs): + # TODO: Fix so that we actually get here with a token request if not settings.IRODS_SODAR_AUTH: logger.error( '{} failed: {}'.format( BASIC_AUTH_LOG_PREFIX, BASIC_AUTH_NOT_ENABLED_MSG ) ) - return HttpResponse(BASIC_AUTH_NOT_ENABLED_MSG, status=500) - if 'HTTP_AUTHORIZATION' not in request.META: - return HttpResponse('Auth header not included', status=400) - user_name = None - user = None - auth = request.META['HTTP_AUTHORIZATION'].split() - if len(auth) == 2 and auth[0].lower() == 'basic': - user_name, password = base64.b64decode(auth[1]).decode().split(':') - logger.debug( - 'Basic auth requested with user name "{}"'.format(user_name) + return JsonResponse( + {'detail': BASIC_AUTH_NOT_ENABLED_MSG}, status=500 ) - # First try with password - user = authenticate(username=user_name, password=password) - # If not successful, try password as Knox token - if not user: - token_auth = TokenAuthentication() - try: - user, _ = token_auth.authenticate_credentials( - password.encode('utf-8') - ) - except Exception as ex: - logger.debug('Basic auth with token failed: {}'.format(ex)) - if user and user.is_authenticated and user.username == user_name: + if request.user.is_authenticated: logger.info( '{} successful: {}'.format( BASIC_AUTH_LOG_PREFIX, request.user.username ) ) - return HttpResponse('Authenticated', status=200) + return JsonResponse({'detail': 'Authenticated'}, status=200) logger.error( '{} failed: User {} not authenticated'.format( BASIC_AUTH_LOG_PREFIX, request.user.username ) ) - return HttpResponse('Unauthorized', status=401) + return JsonResponse({'detail': 'Unauthorized'}, status=401) diff --git a/sodar/users/auth.py b/sodar/users/auth.py index c2be3b6b2..a07bb6377 100644 --- a/sodar/users/auth.py +++ b/sodar/users/auth.py @@ -4,6 +4,7 @@ """ import base64 + from functools import wraps from django.conf import settings @@ -12,6 +13,8 @@ from django.utils.decorators import method_decorator, decorator_from_middleware from django.utils.deprecation import MiddlewareMixin +from knox.auth import TokenAuthentication + class FallbackToAuthBasicMiddleware(MiddlewareMixin): """ @@ -30,17 +33,29 @@ def process_request(self, request): # Allow disabling of basic auth alltogether in configuration. if getattr(settings, 'BASICAUTH_DISABLE', False): return - # When user is not logged in, try to login via HTTP_AUTHORIZATION # header. If this header is not set, send the response to ask for # Auth Basic. - if 'HTTP_AUTHORIZATION' in request.META: - auth = request.META['HTTP_AUTHORIZATION'].split() - if len(auth) == 2 and auth[0].lower() == 'basic': - uname, passwd = base64.b64decode(auth[1]).decode().split(':') - user = authenticate(username=uname, password=passwd) - if user: - login(request, user) + if 'HTTP_AUTHORIZATION' not in request.META: + return + auth = request.META['HTTP_AUTHORIZATION'].split() + if len(auth) == 2 and auth[0].lower() == 'basic': + uname, passwd = base64.b64decode(auth[1]).decode().split(':') + user = authenticate(username=uname, password=passwd) + if user: + login(request, user) + # If not authenticated, try token + token_auth = TokenAuthentication() + try: + user, _ = token_auth.authenticate_credentials( + passwd.encode('utf-8') + ) + except Exception: + return + if user and user.is_authenticated and user.username == uname: + # TODO: How to log in user with token here? + # TODO: Currently we return response here skipping view method.. + return HttpResponse(status=200) def process_response(self, request, response): if not request.user.is_authenticated: