Merge pull request 'sso_propagation_extension' (#483) from sso_propagation_extension into master
Reviewed-on: #483
This commit is contained in:
		
						commit
						d6cabd5f6c
					
				@ -86,7 +86,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        new_title = self.create_dummy_string()
 | 
			
		||||
        new_identifier = self.create_dummy_string()
 | 
			
		||||
        new_comment = self.create_dummy_string()
 | 
			
		||||
        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
 | 
			
		||||
        new_geometry = self.create_dummy_geometry()
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
        test_deductable_surface = self.eco_account.deductable_surface + 100
 | 
			
		||||
 | 
			
		||||
@ -103,7 +103,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            "identifier": new_identifier,
 | 
			
		||||
            "title": new_title,
 | 
			
		||||
            "comment": new_comment,
 | 
			
		||||
            "geom": new_geometry.geojson,
 | 
			
		||||
            "geom": self.create_geojson(new_geometry),
 | 
			
		||||
            "surface": test_deductable_surface,
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
        new_title = self.create_dummy_string()
 | 
			
		||||
        new_identifier = self.create_dummy_string()
 | 
			
		||||
        new_comment = self.create_dummy_string()
 | 
			
		||||
        new_geometry = MultiPolygon(srid=4326)  # Create an empty geometry
 | 
			
		||||
        new_geometry = self.create_dummy_geometry()  # Create an empty geometry
 | 
			
		||||
        test_conservation_office = self.get_conservation_office_code()
 | 
			
		||||
 | 
			
		||||
        check_on_elements = {
 | 
			
		||||
@ -99,7 +99,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            "identifier": new_identifier,
 | 
			
		||||
            "title": new_title,
 | 
			
		||||
            "comment": new_comment,
 | 
			
		||||
            "geom": new_geometry.geojson,
 | 
			
		||||
            "geom": self.create_geojson(new_geometry),
 | 
			
		||||
            "conservation_office": test_conservation_office.id
 | 
			
		||||
        }
 | 
			
		||||
        self.client_user.post(url, post_data)
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,7 @@ class EditInterventionFormTestCase(NewInterventionFormTestCase):
 | 
			
		||||
        self.assertIsNotNone(obj.responsible.handler)
 | 
			
		||||
        self.assertEqual(obj.title, data["title"])
 | 
			
		||||
        self.assertEqual(obj.comment, data["comment"])
 | 
			
		||||
        self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
 | 
			
		||||
        self.assert_equal_geometries(test_geom, obj.geometry.geom)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(obj.legal.binding_date, today)
 | 
			
		||||
        self.assertEqual(obj.legal.registration_date, today)
 | 
			
		||||
 | 
			
		||||
@ -72,9 +72,8 @@ class SimpleGeomForm(BaseForm):
 | 
			
		||||
        # will be rendered again on failed submit
 | 
			
		||||
        self.initialize_form_field("geom", self.data["geom"])
 | 
			
		||||
 | 
			
		||||
        # Read geojson into gdal geometry
 | 
			
		||||
        # HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
 | 
			
		||||
        # this case)
 | 
			
		||||
        # Initialize features list with empty MultiPolygon, so that an empty input will result in a
 | 
			
		||||
        # proper empty MultiPolygon object
 | 
			
		||||
        features = []
 | 
			
		||||
        features_json = geom.get("features", [])
 | 
			
		||||
        accepted_ogr_types = [
 | 
			
		||||
@ -102,19 +101,22 @@ class SimpleGeomForm(BaseForm):
 | 
			
		||||
                return is_valid
 | 
			
		||||
 | 
			
		||||
            is_valid &= self.__is_area_valid(g)
 | 
			
		||||
 | 
			
		||||
            polygon = Polygon.from_ewkt(g.ewkt)
 | 
			
		||||
            is_valid &= polygon.valid
 | 
			
		||||
            if not polygon.valid:
 | 
			
		||||
                self.add_error("geom", polygon.valid_reason)
 | 
			
		||||
            g = Polygon.from_ewkt(g.ewkt)
 | 
			
		||||
            is_valid &= g.valid
 | 
			
		||||
            if not g.valid:
 | 
			
		||||
                self.add_error("geom", g.valid_reason)
 | 
			
		||||
                return is_valid
 | 
			
		||||
 | 
			
		||||
            features.append(polygon)
 | 
			
		||||
            if isinstance(g, Polygon):
 | 
			
		||||
                features.append(g)
 | 
			
		||||
            elif isinstance(g, MultiPolygon):
 | 
			
		||||
                features.extend(list(g))
 | 
			
		||||
 | 
			
		||||
        # Unionize all geometry features into one new MultiPolygon
 | 
			
		||||
        form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
 | 
			
		||||
        for feature in features:
 | 
			
		||||
            form_geom = form_geom.union(feature)
 | 
			
		||||
        if features:
 | 
			
		||||
            form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
 | 
			
		||||
        else:
 | 
			
		||||
            form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
 | 
			
		||||
 | 
			
		||||
        # Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
 | 
			
		||||
        form_geom = Geometry.cast_to_multipolygon(form_geom)
 | 
			
		||||
 | 
			
		||||
@ -395,7 +395,7 @@ class Geometry(BaseResource):
 | 
			
		||||
            output_geom
 | 
			
		||||
        """
 | 
			
		||||
        output_geom = input_geom
 | 
			
		||||
        if input_geom.geom_type != "MultiPolygon":
 | 
			
		||||
        if not isinstance(input_geom, MultiPolygon):
 | 
			
		||||
            output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
 | 
			
		||||
        return output_geom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -469,7 +469,7 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        eco_account.save()
 | 
			
		||||
        return eco_account
 | 
			
		||||
 | 
			
		||||
    def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance = 0.001):
 | 
			
		||||
    def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance=0.001):
 | 
			
		||||
        """ Assert for geometries to be equal
 | 
			
		||||
 | 
			
		||||
        Transforms the geometries to matching srids before checking
 | 
			
		||||
@ -491,7 +491,10 @@ class BaseTestCase(TestCase):
 | 
			
		||||
            # transformation from one coordinate system into the other, which is valid
 | 
			
		||||
            geom1.transform(geom2.srid)
 | 
			
		||||
            geom2.transform(geom1.srid)
 | 
			
		||||
        self.assertTrue(geom1.equals_exact(geom2, tolerance) or geom2.equals_exact(geom1, tolerance))
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            geom1.equals_exact(geom2, tolerance=tolerance),
 | 
			
		||||
            msg=f"Difference is {abs(geom1.area - geom2.area)} with {geom1.area} and {geom2.area} in a tolerance of {tolerance}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseViewTestCase(BaseTestCase):
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ class UserNotificationAdmin(admin.ModelAdmin):
 | 
			
		||||
class UserAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = [
 | 
			
		||||
        "id",
 | 
			
		||||
        "sso_identifier",
 | 
			
		||||
        "username",
 | 
			
		||||
        "first_name",
 | 
			
		||||
        "last_name",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								user/migrations/0010_user_sso_identifier.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								user/migrations/0010_user_sso_identifier.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
# Generated by Django 5.1.6 on 2025-09-12 06:10
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('user', '0009_user_oauth_token'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='user',
 | 
			
		||||
            name='sso_identifier',
 | 
			
		||||
            field=models.CharField(blank=True, db_comment='Identifies the account based on an unique identifier from the SSO system', max_length=255, null=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -6,6 +6,7 @@ Created on: 15.11.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.auth.models import AbstractUser
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
@ -32,6 +33,12 @@ class User(AbstractUser):
 | 
			
		||||
        db_comment="OAuth token for the user",
 | 
			
		||||
        related_name="+"
 | 
			
		||||
    )
 | 
			
		||||
    sso_identifier = models.CharField(
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True,
 | 
			
		||||
        db_comment="Identifies the account based on an unique identifier from the SSO system",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def is_notification_setting_set(self, notification_enum: UserNotificationEnum):
 | 
			
		||||
        return self.notifications.filter(
 | 
			
		||||
@ -264,4 +271,48 @@ class User(AbstractUser):
 | 
			
		||||
            self.oauth_token.delete()
 | 
			
		||||
        self.oauth_token = token
 | 
			
		||||
        self.save()
 | 
			
		||||
        return self
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def resolve_user_using_propagation_data(data: dict):
 | 
			
		||||
        """ Fetches user from db by the given data from propagation process
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            data (dict): json containing user information from the sso system
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            user (User): The resolved user
 | 
			
		||||
        """
 | 
			
		||||
        username = data.get("username", None)
 | 
			
		||||
        sso_identifier = data.get("sso_identifier", None)
 | 
			
		||||
        if not username and not sso_identifier:
 | 
			
		||||
            raise AssertionError("No username or sso identifier provided")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            user = User.objects.get(username=username)
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            try:
 | 
			
		||||
                user = User.objects.get(sso_identifier=sso_identifier)
 | 
			
		||||
            except ObjectDoesNotExist:
 | 
			
		||||
                raise ObjectDoesNotExist("No user with this username or sso identifier was found")
 | 
			
		||||
 | 
			
		||||
        return user
 | 
			
		||||
 | 
			
		||||
    def update_user_using_propagation_data(self, data: dict):
 | 
			
		||||
        """ Update user data based on propagation data from sso system
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            data (dict): json containing user information from the sso system
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            user (User): The updated user
 | 
			
		||||
        """
 | 
			
		||||
        skipable_attrs = {
 | 
			
		||||
            "is_staff",
 | 
			
		||||
            "is_superuser",
 | 
			
		||||
        }
 | 
			
		||||
        for _attr, _val in data.items():
 | 
			
		||||
            if _attr in skipable_attrs:
 | 
			
		||||
                continue
 | 
			
		||||
            setattr(self, _attr, _val)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
@ -44,17 +44,8 @@ class PropagateUserView(View):
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            status = "updated"
 | 
			
		||||
            user = User.objects.get(username=body.get('username'))
 | 
			
		||||
            # Update user data, excluding some changes
 | 
			
		||||
            skipable_attrs = {
 | 
			
		||||
                "username",
 | 
			
		||||
                "is_staff",
 | 
			
		||||
                "is_superuser",
 | 
			
		||||
            }
 | 
			
		||||
            for _attr, _val in body.items():
 | 
			
		||||
                if _attr in skipable_attrs:
 | 
			
		||||
                    continue
 | 
			
		||||
                setattr(user, _attr, _val)
 | 
			
		||||
            user = User.resolve_user_using_propagation_data(body)
 | 
			
		||||
            user = user.update_user_using_propagation_data(body)
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            user = User(**body)
 | 
			
		||||
            status = "created"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user