diff --git a/docker-compose.yml b/docker-compose.yml index e1e70513f69..f3fa5f12280 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ x-common-django: env_file: - .env volumes: + - '.:/usr/src/geonode' - statics:/mnt/volumes/statics - geoserver-data-dir:/geoserver_data/data - backup-restore:/backup_restore @@ -141,8 +142,8 @@ services: healthcheck: test: "pg_isready -d postgres -U postgres" # uncomment to enable remote connections to postgres - #ports: - # - "5432:5432" + ports: + - "${DB_EXPOSE_PORT}:5432" # Vanilla RabbitMQ service. This is needed by celery rabbitmq: diff --git a/geonode/api/authorization.py b/geonode/api/authorization.py index 376d53c8e8b..3bc80749b55 100644 --- a/geonode/api/authorization.py +++ b/geonode/api/authorization.py @@ -22,6 +22,8 @@ from tastypie.exceptions import Unauthorized from tastypie.compat import get_user_model, get_username_field +from rest_framework import authentication + from guardian.shortcuts import get_objects_for_user from tastypie.http import HttpUnauthorized @@ -110,7 +112,34 @@ def is_authenticated(self, request, **kwargs): return key_auth_check - +class GeonodeTokenAuthentication(authentication.TokenAuthentication): + ''' + Simple token based authentication using utvsapitoken. + Clients should authenticate by passing the token key in the 'Authorization' + HTTP header, prepended with the string 'Bearer '. For example: + Authorization: Bearer 956e252a-513c-48c5-92dd-bfddc364e812 + ''' + keyword = ['token','bearer'] + def authenticate(self, request): + auth = authentication.get_authorization_header(request).split() + if not auth: + return None + if auth[0].lower().decode() not in self.keyword: + return None + + if len(auth) == 1: + msg = _('Invalid token header. No credentials provided.') + raise authentication.exceptions.AuthenticationFailed(msg) + elif len(auth) > 2: + msg = _('Invalid token header. Token string should not contain spaces.') + raise authentication.exceptions.AuthenticationFailed(msg) + try: + token = auth[1].decode() + except UnicodeError: + msg = _('Invalid token header. Token string should not contain invalid characters.') + raise authentication.TokenAuthentication.exceptions.AuthenticationFailed(msg) + return self.authenticate_credentials(token) + class GeoNodeStyleAuthorization(GeoNodeAuthorization): """Object level API authorization based on GeoNode granular permission system diff --git a/geonode/api/resourcebase_api.py b/geonode/api/resourcebase_api.py index cf98190ac8e..16cc8896941 100644 --- a/geonode/api/resourcebase_api.py +++ b/geonode/api/resourcebase_api.py @@ -46,7 +46,7 @@ from geonode.utils import check_ogc_backend from geonode.security.utils import get_visible_resources from .authentication import OAuthAuthentication -from .authorization import GeoNodeAuthorization, GeonodeApiKeyAuthentication +from .authorization import GeoNodeAuthorization, GeonodeApiKeyAuthentication, GeonodeTokenAuthentication from .api import ( TagResource, @@ -348,7 +348,7 @@ class Meta(CommonMetaApi): resource_name = "base" excludes = ["csw_anytext", "metadata_xml"] authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) @@ -360,7 +360,7 @@ class Meta(CommonMetaApi): queryset = ResourceBase.objects.filter(featured=True).order_by("-date") resource_name = "featured" authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) @@ -492,7 +492,7 @@ class Meta(CommonMetaApi): allowed_methods = ["get", "patch"] excludes = ["csw_anytext", "metadata_xml"] authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) filtering = CommonMetaApi.filtering # Allow filtering using ID @@ -560,7 +560,7 @@ class Meta(CommonMetaApi): queryset = Map.objects.distinct().order_by("-date") resource_name = "maps" authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) @@ -611,7 +611,7 @@ class Meta(CommonMetaApi): queryset = GeoApp.objects.distinct().order_by("-date") resource_name = "geoapps" authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) @@ -669,5 +669,5 @@ class Meta(CommonMetaApi): queryset = Document.objects.distinct().order_by("-date") resource_name = "documents" authentication = MultiAuthentication( - SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication() + SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication() ) diff --git a/geonode/api/views.py b/geonode/api/views.py index b43eb9c13e3..d11e6b4cbee 100644 --- a/geonode/api/views.py +++ b/geonode/api/views.py @@ -32,8 +32,10 @@ from ..utils import json_response from ..decorators import superuser_or_apiauth -from ..base.auth import get_token_object_from_session, get_auth_token +from ..base.auth import get_token_object_from_session, get_auth_token, create_auth_token +from rest_framework import exceptions +from rest_framework.authtoken.models import Token def verify_access_token(request, key): try: @@ -86,6 +88,21 @@ def user_info(request): return response +def auth_api_token(request): + key = request.POST.get("token") + + if not key: + return None + + try: + token = Token.objects.select_related('user').get(key=key) + except Token.DoesNotExist: + raise exceptions.AuthenticationFailed('Invalid token.') + + if not token.user.is_active: + raise exceptions.AuthenticationFailed('User inactive or deleted.') + + return token.user @csrf_exempt def verify_token(request): @@ -95,7 +112,11 @@ def verify_token(request): access_token = request.POST.get("token") token = verify_access_token(request, access_token) except Exception as e: - return HttpResponse(json.dumps({"error": str(e)}), status=403, content_type="application/json") + usr = auth_api_token(request) + if usr: + token = create_auth_token(usr) + else: + return HttpResponse(json.dumps({"error": str(e)}), status=403, content_type="application/json") if token: token_info = json.dumps( diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 4d645a8624e..f7f7dbd0f2b 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -76,7 +76,27 @@ def user_serializer(): return ser.UserSerializer +class RegistrationSerializer(serializers.ModelSerializer): + password2 = serializers.CharField(style={"input_type": "password"}, write_only=True) + class Meta: + model = get_user_model() + fields = ['username', 'password', 'password2'] + extra_kwargs = { + 'password': {'write_only': True} + } + + def save(self): + UserModel = get_user_model() + user = UserModel(username=self.validated_data['username']) + password = self.validated_data['password'] + password2 = self.validated_data['password2'] + if password != password2: + raise serializers.ValidationError({'password': 'Passwords must match.'}) + user.set_password(password) + user.save() + return user + class BaseDynamicModelSerializer(DynamicModelSerializer): def to_representation(self, instance): data = super().to_representation(instance) diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index 8fbbdf71407..4a5c1d952db 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -88,6 +88,8 @@ UserHasPerms, ) +from geonode.api.authorization import GeonodeTokenAuthentication + from .serializers import ( FavoriteSerializer, PermSpecSerialiazer, @@ -116,7 +118,7 @@ class GroupViewSet(DynamicModelViewSet): API endpoint that allows gropus to be viewed or edited. """ - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication, GeonodeTokenAuthentication] permission_classes = [ IsAuthenticatedOrReadOnly, IsManagerEditOrAdmin, @@ -178,6 +180,7 @@ class RegionViewSet(WithDynamicViewSetMixin, ListModelMixin, RetrieveModelMixin, API endpoint that lists regions. """ + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ AllowAny, ] @@ -232,6 +235,7 @@ class TopicCategoryViewSet(WithDynamicViewSetMixin, ListModelMixin, RetrieveMode API endpoint that lists categories. """ + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ AllowAny, ] @@ -246,7 +250,7 @@ class OwnerViewSet(WithDynamicViewSetMixin, ListModelMixin, RetrieveModelMixin, API endpoint that lists all possible owners. """ - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication, GeonodeTokenAuthentication] permission_classes = [ AllowAny, ] @@ -302,7 +306,7 @@ class ResourceBaseViewSet(ApiPresetsInitializer, DynamicModelViewSet, Advertised API endpoint that allows base resources to be viewed or edited. """ - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication, GeonodeTokenAuthentication] permission_classes = [IsAuthenticatedOrReadOnly, UserHasPerms] filter_backends = [ TKeywordsFilter, diff --git a/geonode/documents/api/views.py b/geonode/documents/api/views.py index d8fe1ca2395..151eb0e8daa 100644 --- a/geonode/documents/api/views.py +++ b/geonode/documents/api/views.py @@ -45,6 +45,8 @@ from .serializers import DocumentSerializer from .permissions import DocumentPermissionsFilter +from geonode.api.authorization import GeonodeTokenAuthentication + import logging logger = logging.getLogger(__name__) @@ -56,7 +58,7 @@ class DocumentViewSet(ApiPresetsInitializer, DynamicModelViewSet, AdvertisedList """ http_method_names = ["get", "patch", "put", "post"] - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ IsAuthenticatedOrReadOnly, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}}), diff --git a/geonode/geoapps/api/views.py b/geonode/geoapps/api/views.py index 57136b9e771..f3bcf06a8f2 100644 --- a/geonode/geoapps/api/views.py +++ b/geonode/geoapps/api/views.py @@ -33,6 +33,8 @@ from .serializers import GeoAppSerializer from .permissions import GeoAppPermissionsFilter +from geonode.api.authorization import GeonodeTokenAuthentication + import logging logger = logging.getLogger(__name__) @@ -44,7 +46,7 @@ class GeoAppViewSet(ApiPresetsInitializer, DynamicModelViewSet, AdvertisedListMi """ http_method_names = ["get", "patch", "post", "put"] - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ IsAuthenticatedOrReadOnly, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}}), diff --git a/geonode/geoserver/helpers.py b/geonode/geoserver/helpers.py index f3851567223..dcd1d1a315d 100755 --- a/geonode/geoserver/helpers.py +++ b/geonode/geoserver/helpers.py @@ -25,6 +25,7 @@ import json import errno import typing +import secrets import logging import datetime import tempfile @@ -363,7 +364,22 @@ def get_sld_for(gs_catalog, layer): else: return gs_style +def edit_dataset_style(style,sld): + style_to_edit = gs_catalog.get_style(name=style.name, workspace=style.workspace) + if style_to_edit: + + if sld and isinstance(sld, str): + sld = sld.strip("b'\n") + sld = re.sub(r"(\\r)|(\\n)", "", sld).encode("UTF-8") + + style = gs_catalog.create_style( + style.name, sld, overwrite=True, raw=True, workspace=style.workspace + ) + return style + else: + return None + def set_dataset_style(saved_dataset, title, sld, base_file=None): # Check SLD is valid try: @@ -408,9 +424,9 @@ def set_dataset_style(saved_dataset, title, sld, base_file=None): try: _sld_format = _extract_style_version_from_sld(sld) style = gs_catalog.create_style( - saved_dataset.name, + f'{saved_dataset.name}_{secrets.token_hex(nbytes=2)}', sld, - overwrite=True, + overwrite=False, raw=True, style_format=_sld_format, workspace=saved_dataset.workspace, @@ -429,9 +445,16 @@ def set_dataset_style(saved_dataset, title, sld, base_file=None): ) layer.default_style = style gs_catalog.save(layer) + + current_styles = layer._get_alternate_styles() + current_styles.append(style) + layer._set_alternate_styles(current_styles) + gs_catalog.save(layer) for _s in _old_styles: try: - gs_catalog.delete(_s) + time_delta = datetime.datetime.now(datetime.timezone.utc) - saved_dataset.created + if time_delta.seconds < 120: + gs_catalog.delete(_s) Link.objects.filter( resource=saved_dataset.resourcebase_ptr, name="Legend", url__contains=f"STYLE={_s.name}" ).delete() @@ -890,6 +913,19 @@ def gs_slurp( output["stats"]["duration_sec"] = td.microseconds / 1000000 + td.seconds + td.days * 24 * 3600 return output +def delete_style_gs(layer,style): + style_to_delete = gs_catalog.get_style(name=style.name,workspace=style.workspace) + + layer = gs_catalog.get_layer(layer.alternate) + current_styles = layer._get_alternate_styles() + layer._set_alternate_styles([x for x in current_styles if x.name != style_to_delete.name]) + gs_catalog.save(layer) + + if style_to_delete: + gs_catalog.delete(style_to_delete, purge=True, recurse=False) + logger.debug(f"set_style: No-ws default style deleted: {style.name}") + else: + logger.debug(f"set_style: No-ws default style does not exist: {style.name}") def get_stores(store_type=None): cat = gs_catalog @@ -1137,9 +1173,24 @@ def clean_styles(layer, gs_catalog: Catalog): logger.warning(f"Could not clean style for layer {layer.name}", exc_info=e) logger.debug(f"Could not clean style for layer {layer.name} - STACK INFO", stack_info=True) +def change_default_style(layer,style): + + gs_dataset = get_dataset(layer, gs_catalog) + + _new_default_style = gs_catalog.get_style(name=style.name,workspace=style.workspace) + + gs_dataset.default_style = _new_default_style + gs_catalog.save(gs_dataset) + def set_styles(layer, gs_catalog: Catalog): style_set = [] + + time_delta = datetime.datetime.now(datetime.timezone.utc) - layer.created + if time_delta.seconds > 120: + for _style in layer.styles.all(): + style_set.append(_style) + gs_dataset = get_dataset(layer, gs_catalog) if gs_dataset: default_style = gs_dataset.get_full_default_style() diff --git a/geonode/layers/api/views.py b/geonode/layers/api/views.py index 35fdbc2f18b..0ab592dc67f 100644 --- a/geonode/layers/api/views.py +++ b/geonode/layers/api/views.py @@ -35,13 +35,14 @@ from geonode.base.api.views import ApiPresetsInitializer from geonode.layers.api.exceptions import GeneralDatasetException, InvalidDatasetException, InvalidMetadataException from geonode.layers.metadata import parse_metadata -from geonode.layers.models import Dataset +from geonode.layers.models import Dataset, Style from geonode.maps.api.serializers import SimpleMapLayerSerializer, SimpleMapSerializer from geonode.resource.utils import update_resource from geonode.resource.manager import resource_manager from rest_framework.exceptions import NotFound from geonode.storage.manager import StorageManager +from geonode.geoserver.helpers import set_dataset_style, delete_style_gs, edit_dataset_style, change_default_style from .serializers import ( DatasetSerializer, @@ -50,6 +51,8 @@ ) from .permissions import DatasetPermissionsFilter +from geonode.api.authorization import GeonodeTokenAuthentication + import logging logger = logging.getLogger(__name__) @@ -61,7 +64,7 @@ class DatasetViewSet(ApiPresetsInitializer, DynamicModelViewSet, AdvertisedListM """ http_method_names = ["get", "patch", "put"] - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ IsAuthenticatedOrReadOnly, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}}), @@ -90,6 +93,141 @@ def partial_update(self, request, *args, **kwargs): return result + @action( + detail=False, + url_path="(?P\d+)/add_new_style", # noqa + url_name="add-new-style", + methods=["put"], + serializer_class=DatasetSerializer, + permission_classes=[ + IsAuthenticated + ], + ) + def add_new_style(self, request, pk): + try: + layer = Dataset.objects.get(id=pk) + + if 'sld_style' in request.data: + set_dataset_style(layer,layer.alternate,request.data['sld_style']) + return Response(DatasetSerializer(layer).data) + except Dataset.DoesNotExist: + return Response({"error": "The dataset you requested does not exists" }) + + @action( + detail=False, + url_path="(?P\d+)/change_default_style", # noqa + url_name="change-default-style", + methods=["put"], + serializer_class=DatasetSerializer, + permission_classes=[ + IsAuthenticated + ], + ) + def change_default_style(self, request, pk): + layer = None + try: + layer = Dataset.objects.get(id=pk) + + except Dataset.DoesNotExist: + return Response({"error": "The dataset you requested does not exists" }) + + + if layer and 'default_style_pk' in request.data: + try: + style = Style.objects.get(id=request.data['default_style_pk']) + change_default_style(layer,style) + layer.default_style = style + layer.save() + return Response(DatasetSerializer(layer).data) + except Style.DoesNotExist: + return Response({"error": "The style you specified does not exists" }) + + @action( + detail=False, + url_path="(?P\d+)/delete_style", # noqa + url_name="delete-style", + methods=["put"], + serializer_class=DatasetSerializer, + permission_classes=[ + IsAuthenticated + ], + ) + def delete_style(self, request, pk): + layer = None + try: + layer = Dataset.objects.get(id=pk) + + except Dataset.DoesNotExist: + return Response({"error": "The dataset you requested does not exists" }) + + + if layer and 'style_pk' in request.data: + try: + style = Style.objects.get(id=request.data['style_pk']) + if layer.default_style == style: + if len(list(layer.styles.all())) > 1: + if layer.styles.all()[0] != style: + change_default_style(layer,layer.styles.all()[0]) + delete_style_gs(layer,style) + layer.default_style = layer.styles.all()[0] + layer.save() + style.delete() + else: + total_styles = len(list(layer.styles.all())) + change_default_style(layer,layer.styles.all()[total_styles - 1]) + delete_style_gs(layer,style) + layer.default_style = layer.styles.all()[total_styles - 1] + layer.save() + style.delete() + else: + return Response({"error": "Cannot delete default style, no other styles present for dataset" }) + else: + delete_style_gs(layer,style) + style.delete() + return Response(DatasetSerializer(layer).data) + except Style.DoesNotExist: + return Response({"error": "The style you specified does not exists" }) + else: + return Response({"error": "You must provide style_pk to delete a style" }) + + @action( + detail=False, + url_path="(?P\d+)/edit_style", # noqa + url_name="edit-style", + methods=["put"], + serializer_class=DatasetSerializer, + permission_classes=[ + IsAuthenticated + ], + ) + def edit_style(self, request, pk): + layer = None + try: + layer = Dataset.objects.get(id=pk) + + except Dataset.DoesNotExist: + return Response({"error": "The dataset you requested does not exists" }) + + + if layer and 'style_pk' in request.data and 'style_sld' in request.data: + try: + style = Style.objects.get(id=request.data['style_pk']) + + new_style = edit_dataset_style(style,request.data['style_sld']) + + if new_style: + style.sld_title = new_style.sld_title + style.save() + + return Response(DatasetSerializer(layer).data) + else: + return Response({"error": "There was an error with the update, check your data" }) + + except Style.DoesNotExist: + return Response({"error": "The style you specified does not exists" }) + else: + return Response({"error": "TYou must provide the fields style_pk, style_sld and sld_title to update a style" }) + @extend_schema( request=DatasetMetadataSerializer, methods=["put"], diff --git a/geonode/maps/api/views.py b/geonode/maps/api/views.py index 6ad3febe547..ecf53c1fca8 100644 --- a/geonode/maps/api/views.py +++ b/geonode/maps/api/views.py @@ -46,6 +46,8 @@ from geonode.resource.manager import resource_manager from geonode.utils import resolve_object +from geonode.api.authorization import GeonodeTokenAuthentication + logger = logging.getLogger(__name__) @@ -55,7 +57,7 @@ class MapViewSet(ApiPresetsInitializer, DynamicModelViewSet, AdvertisedListMixin """ http_method_names = ["get", "patch", "post", "put"] - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [ IsAuthenticatedOrReadOnly, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}}), diff --git a/geonode/people/api/views.py b/geonode/people/api/views.py index 5e9cf680e46..53a66900af3 100644 --- a/geonode/people/api/views.py +++ b/geonode/people/api/views.py @@ -22,6 +22,10 @@ from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 +from rest_framework import status +from geonode.api.authorization import GeonodeTokenAuthentication + +from .serializers import RegistrationSerializer class UserViewSet(DynamicModelViewSet): """ @@ -29,7 +33,7 @@ class UserViewSet(DynamicModelViewSet): """ http_method_names = ["get", "post", "patch", "delete"] - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication, GeonodeTokenAuthentication] permission_classes = [ IsAuthenticated, IsOwnerOrAdmin, @@ -38,6 +42,13 @@ class UserViewSet(DynamicModelViewSet): serializer_class = UserSerializer pagination_class = GeoNodeApiPagination + def create(self, request): + serializer = RegistrationSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def get_queryset(self): """ Filters and sorts users. diff --git a/geonode/resource/api/views.py b/geonode/resource/api/views.py index c99c11c51b9..a23ddfb49f6 100644 --- a/geonode/resource/api/views.py +++ b/geonode/resource/api/views.py @@ -39,6 +39,8 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet +from geonode.api.authorization import GeonodeTokenAuthentication + from ..models import ExecutionRequest from .utils import filtered, resolve_type_serializer @@ -131,7 +133,7 @@ class ExecutionRequestViewset(WithDynamicViewSetMixin, ListModelMixin, RetrieveM API endpoint that allows users to be viewed or edited. """ - authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication] + authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication] permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] filter_backends = [DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter] serializer_class = ExecutionRequestSerializer diff --git a/geonode/settings.py b/geonode/settings.py index 70651fb21ba..87932713197 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -479,6 +479,7 @@ "django_user_agents", # REST APIs "rest_framework", + "rest_framework.authtoken", "rest_framework_gis", "dynamic_rest", "drf_spectacular", diff --git a/geonode/urls.py b/geonode/urls.py index ccab35e950f..39268b8f592 100644 --- a/geonode/urls.py +++ b/geonode/urls.py @@ -30,6 +30,7 @@ from django.conf.urls.i18n import i18n_patterns from django.views.i18n import JavaScriptCatalog from django.contrib.sitemaps.views import sitemap +from rest_framework.authtoken import views as rest_views import geonode.proxy.urls from . import views @@ -126,6 +127,7 @@ re_path(r"^api/v2/", include("geonode.api.urls")), re_path(r"^api/v2/", include("geonode.management_commands_http.urls")), re_path(r"^api/v2/api-auth/", include("rest_framework.urls", namespace="geonode_rest_framework")), + re_path(r"^api-token-auth/", rest_views.obtain_auth_token), re_path(r"^api/v2/", include("geonode.facets.urls")), re_path(r"", include(api.urls)), ] diff --git a/scripts/misc/nginx_integration.conf b/scripts/misc/nginx_integration.conf index 7a8448b2675..91d982dd393 100644 --- a/scripts/misc/nginx_integration.conf +++ b/scripts/misc/nginx_integration.conf @@ -48,20 +48,22 @@ http { return 200; } - client_max_body_size 15M; - client_body_buffer_size 128K; + client_max_body_size 1G; + client_body_buffer_size 10M; add_header Access-Control-Allow-Credentials false; add_header Access-Control-Allow-Headers "Content-Type, Accept, Authorization, Origin, User-Agent"; add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, OPTIONS"; - proxy_read_timeout 30; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Host $server_name; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_hide_header X-Frame-Options; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + send_timeout 600; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $server_name; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; error_log /tmp/error.log; access_log /tmp/access.log; diff --git a/uwsgi.ini b/uwsgi.ini index c519637152e..305df17802f 100644 --- a/uwsgi.ini +++ b/uwsgi.ini @@ -26,6 +26,7 @@ max-requests = 1000 ; Restart workers after this many requests max-worker-lifetime = 3600 ; Restart workers after this many seconds reload-on-rss = 2048 ; Restart workers after this much resident memory worker-reload-mercy = 60 ; How long to wait before forcefully killing workers +py-autoreload = 1 cheaper-algo = busyness processes = 128 ; Maximum number of workers allowed