Categories
Mastering Development

django-private-storage configuration in nginx and Docker

I am trying to use django-private-storage package to protect a model’s file from being accessed
or downloaded by users who are not owners of the file.

I was able to do it successfully in development (using python manage.py runserver)

In development, I am using nginx configured via docker.

I can create objects with both FileField and PrivateFileField. My problem is accessing the urls associated with a PrivateFileField.

The mediafiles are being served as expected (e.g. when I access the url of a FileField), but I get a “404 Not Found” error from nginx when I access the url of a PrivateFileField.

My hunch is that the server response is not properly configured to have a ‘X-Accel-Redirect’ data,
thus treating the response not internal.

If I remove the line “internal;” in my nginx.conf for the private-data location, the PrivateFile is served
properly, although, now, it is not private.

    location /private-data/ {
        internal;  #<------ the PrivateFile can be accessed  if this line is removed
        alias /home/app/web/private-data/;
    }

Also, I am sure that the private file was saved in /home/app/web/private-data

Am I missing out something in the implementation?

Thanks in advance.

Additional info:

FILES ———————————–

docker-compose.prod.yml

version: '3.7'

services:
  web:
    build:
      context: ./web_app
      dockerfile: Dockerfile.prod
    command: gunicorn notify_django_project.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
      - private_volume:/home/app/web/private-data
    expose:
      - 8000
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:12.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db
  nginx:
    build: ./nginx
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
      - private_volume:/home/app/web/private-data
    ports:
      - 1337:80
    depends_on:
      - web

volumes:
  postgres_data:
  static_volume:
  media_volume:
  private_volume:

nginx.conf

upstream django_project {
    server web:8000;
}

server {

    listen 80;

    location / {
        proxy_pass http://django_project;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /staticfiles/ {
        alias /home/app/web/staticfiles/;
    }

    location /mediafiles/ {
        alias /home/app/web/mediafiles/;
    }

    location /private-data/ {
        internal;
        alias /home/app/web/private-data/;
    }

}

settings.py

INSTALLED_APPS = [
    ...
    'private_storage',
    ....
]

PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, "private-data")
PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_authenticated'
PRIVATE_STORAGE_INTERNAL_URL = '/private-data/'
PRIVATE_STORAGE_SERVER = 'nginx'

models.py

class Message(models.Model):
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False)
    subject = models.CharField(max_length=255)
    attachment = models.FileField(upload_to=get_attachment_save_path, null=True, blank=True)
    private_attachment = PrivateFileField(upload_subfolder=get_private_attachment_save_path, null=True, blank=True)

urls.py

urlpatterns = [
    ...
    path('private-data/<str:code>/<int:year>/<str:subdir>/<uuid:pk>/<str:filename>', DownloadPrivateFileView.as_view(), name="file_download"),
    url('^private-data/', include(private_storage.urls)),
    ...
]

views.py

@method_decorator(login_required, name='dispatch')
class DownloadPrivateFileView(PrivateStorageDetailView):
    model = Message
    model_file_field = 'private_attachment'

    def can_access_file(self, private_file):
        # When the object can be accessed, the file may be downloaded.
        # This overrides PRIVATE_STORAGE_AUTH_FUNCTION

        # grant_access checks private_file ownership
        grant_access = grant_note_access(private_file.request, message=self.get_object())
        return grant_access

Leave a Reply

Your email address will not be published. Required fields are marked *