mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Enhancement: support remote user auth directly against API (DRF) (#5386)
This commit is contained in:
		@@ -139,7 +139,7 @@ document. Paperless only reports PDF metadata at this point.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Authorization
 | 
					## Authorization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The REST api provides three different forms of authentication.
 | 
					The REST api provides four different forms of authentication.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1.  Basic authentication
 | 
					1.  Basic authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -177,6 +177,12 @@ The REST api provides three different forms of authentication.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Tokens can also be managed in the Django admin.
 | 
					    Tokens can also be managed in the Django admin.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.  Remote User authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If already setup (see
 | 
				
			||||||
 | 
					    [configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER)),
 | 
				
			||||||
 | 
					    you can authenticate against the API using Remote User auth.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Searching for documents
 | 
					## Searching for documents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Full text searching is available on the `/api/documents/` endpoint. Two
 | 
					Full text searching is available on the `/api/documents/` endpoint. Two
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,3 +47,11 @@ class HttpRemoteUserMiddleware(PersistentRemoteUserMiddleware):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    header = settings.HTTP_REMOTE_USER_HEADER_NAME
 | 
					    header = settings.HTTP_REMOTE_USER_HEADER_NAME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaperlessRemoteUserAuthentication(authentication.RemoteUserAuthentication):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    REMOTE_USER authentication for DRF which overrides the default header.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    header = settings.HTTP_REMOTE_USER_HEADER_NAME
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -420,18 +420,30 @@ if AUTO_LOGIN_USERNAME:
 | 
				
			|||||||
    # regular login in case the provided user does not exist.
 | 
					    # regular login in case the provided user does not exist.
 | 
				
			||||||
    MIDDLEWARE.insert(_index + 1, "paperless.auth.AutoLoginMiddleware")
 | 
					    MIDDLEWARE.insert(_index + 1, "paperless.auth.AutoLoginMiddleware")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENABLE_HTTP_REMOTE_USER = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
 | 
					
 | 
				
			||||||
HTTP_REMOTE_USER_HEADER_NAME = os.getenv(
 | 
					def _parse_remote_user_settings() -> str:
 | 
				
			||||||
 | 
					    global MIDDLEWARE, AUTHENTICATION_BACKENDS, REST_FRAMEWORK
 | 
				
			||||||
 | 
					    enable = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
 | 
				
			||||||
 | 
					    if enable:
 | 
				
			||||||
 | 
					        MIDDLEWARE.append("paperless.auth.HttpRemoteUserMiddleware")
 | 
				
			||||||
 | 
					        AUTHENTICATION_BACKENDS.insert(
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            "django.contrib.auth.backends.RemoteUserBackend",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].insert(
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            "paperless.auth.PaperlessRemoteUserAuthentication",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    header_name = os.getenv(
 | 
				
			||||||
        "PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME",
 | 
					        "PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME",
 | 
				
			||||||
        "HTTP_REMOTE_USER",
 | 
					        "HTTP_REMOTE_USER",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ENABLE_HTTP_REMOTE_USER:
 | 
					    return header_name
 | 
				
			||||||
    MIDDLEWARE.append("paperless.auth.HttpRemoteUserMiddleware")
 | 
					
 | 
				
			||||||
    AUTHENTICATION_BACKENDS.insert(0, "django.contrib.auth.backends.RemoteUserBackend")
 | 
					
 | 
				
			||||||
    REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append(
 | 
					HTTP_REMOTE_USER_HEADER_NAME = _parse_remote_user_settings()
 | 
				
			||||||
        "rest_framework.authentication.RemoteUserAuthentication",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# X-Frame options for embedded PDF display:
 | 
					# X-Frame options for embedded PDF display:
 | 
				
			||||||
X_FRAME_OPTIONS = "ANY" if DEBUG else "SAMEORIGIN"
 | 
					X_FRAME_OPTIONS = "ANY" if DEBUG else "SAMEORIGIN"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								src/paperless/tests/test_remote_user.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/paperless/tests/test_remote_user.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
 | 
					from rest_framework import status
 | 
				
			||||||
 | 
					from rest_framework.test import APITestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from documents.tests.utils import DirectoriesMixin
 | 
				
			||||||
 | 
					from paperless.settings import _parse_remote_user_settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestRemoteUser(DirectoriesMixin, APITestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        super().setUp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.user = User.objects.create_superuser(
 | 
				
			||||||
 | 
					            username="temp_admin",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_remote_user(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        GIVEN:
 | 
				
			||||||
 | 
					            - Configured user
 | 
				
			||||||
 | 
					            - Remote user auth is enabled
 | 
				
			||||||
 | 
					        WHEN:
 | 
				
			||||||
 | 
					            - API call is made to get documents
 | 
				
			||||||
 | 
					        THEN:
 | 
				
			||||||
 | 
					            - Call succeeds
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with mock.patch.dict(
 | 
				
			||||||
 | 
					            os.environ,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "PAPERLESS_ENABLE_HTTP_REMOTE_USER": "True",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            _parse_remote_user_settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response = self.client.get("/api/documents/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # 403 testing locally, 401 on ci...
 | 
				
			||||||
 | 
					            self.assertIn(
 | 
				
			||||||
 | 
					                response.status_code,
 | 
				
			||||||
 | 
					                [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response = self.client.get(
 | 
				
			||||||
 | 
					                "/api/documents/",
 | 
				
			||||||
 | 
					                headers={
 | 
				
			||||||
 | 
					                    "Remote-User": self.user.username,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_remote_user_header_setting(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        GIVEN:
 | 
				
			||||||
 | 
					            - Remote user header name is set
 | 
				
			||||||
 | 
					        WHEN:
 | 
				
			||||||
 | 
					            - Settings are parsed
 | 
				
			||||||
 | 
					        THEN:
 | 
				
			||||||
 | 
					            - Correct header name is returned
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with mock.patch.dict(
 | 
				
			||||||
 | 
					            os.environ,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "PAPERLESS_ENABLE_HTTP_REMOTE_USER": "True",
 | 
				
			||||||
 | 
					                "PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME": "HTTP_FOO",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            header_name = _parse_remote_user_settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(header_name, "HTTP_FOO")
 | 
				
			||||||
		Reference in New Issue
	
	Block a user