Skip to content

Commit

Permalink
feat: backport code from 4.2
Browse files Browse the repository at this point in the history
  • Loading branch information
jdonlucas committed Aug 15, 2024
1 parent 55d24aa commit 8dbddf6
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 35 deletions.
5 changes: 3 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
31 changes: 30 additions & 1 deletion geonode/api/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions geonode/api/resourcebase_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -348,7 +348,7 @@ class Meta(CommonMetaApi):
resource_name = "base"
excludes = ["csw_anytext", "metadata_xml"]
authentication = MultiAuthentication(
SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication()
SessionAuthentication(), OAuthAuthentication(), GeonodeApiKeyAuthentication(), GeonodeTokenAuthentication()
)


Expand All @@ -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()
)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
)


Expand Down Expand Up @@ -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()
)


Expand Down Expand Up @@ -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()
)
25 changes: 23 additions & 2 deletions geonode/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand All @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions geonode/base/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 7 additions & 3 deletions geonode/base/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
UserHasPerms,
)

from geonode.api.authorization import GeonodeTokenAuthentication

from .serializers import (
FavoriteSerializer,
PermSpecSerialiazer,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -178,6 +180,7 @@ class RegionViewSet(WithDynamicViewSetMixin, ListModelMixin, RetrieveModelMixin,
API endpoint that lists regions.
"""

authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication]
permission_classes = [
AllowAny,
]
Expand Down Expand Up @@ -232,6 +235,7 @@ class TopicCategoryViewSet(WithDynamicViewSetMixin, ListModelMixin, RetrieveMode
API endpoint that lists categories.
"""

authentication_classes = [SessionAuthentication, BasicAuthentication, GeonodeTokenAuthentication, OAuth2Authentication]
permission_classes = [
AllowAny,
]
Expand All @@ -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,
]
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion geonode/documents/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
from .serializers import DocumentSerializer
from .permissions import DocumentPermissionsFilter

from geonode.api.authorization import GeonodeTokenAuthentication

import logging

logger = logging.getLogger(__name__)
Expand All @@ -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"]}}),
Expand Down
4 changes: 3 additions & 1 deletion geonode/geoapps/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from .serializers import GeoAppSerializer
from .permissions import GeoAppPermissionsFilter

from geonode.api.authorization import GeonodeTokenAuthentication

import logging

logger = logging.getLogger(__name__)
Expand All @@ -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"]}}),
Expand Down
57 changes: 54 additions & 3 deletions geonode/geoserver/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import json
import errno
import typing
import secrets
import logging
import datetime
import tempfile
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 8dbddf6

Please sign in to comment.