From 36ff7850086838ac4487c4552040a833fe0d8662 Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Thu, 21 Sep 2023 17:51:34 +0200 Subject: [PATCH 1/3] Fix(Workspaces): ensure the presence of a token (and a server hash) for each membership --- ...rkspacemembership_access_token_and_more.py | 36 +++++++++++++++++++ hexa/workspaces/models.py | 11 +++--- 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py diff --git a/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py b/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py new file mode 100644 index 000000000..f4602ff3f --- /dev/null +++ b/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.1.7 on 2023-09-21 15:44 + +from django.db import migrations, models + + +def generate_tokens(apps, schema_editor): + WorkspaceMembership = apps.get_model("workspaces", "WorkspaceMembership") + + # Filter the instances without access_token + memberships_without_token = WorkspaceMembership.objects.filter( + models.Q(access_token__isnull=True) | models.Q(access_token="") + ) + + for membership in memberships_without_token: + membership.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("workspaces", "0036_service_account_key_migration"), + ] + + operations = [ + migrations.RunPython(generate_tokens, reverse_code=migrations.RunPython.noop), + migrations.AlterField( + model_name="workspacemembership", + name="access_token", + field=models.TextField(max_length=50, unique=True), + ), + migrations.AlterField( + model_name="workspacemembership", + name="notebooks_server_hash", + field=models.TextField(unique=True), + ), + ] diff --git a/hexa/workspaces/models.py b/hexa/workspaces/models.py index 6294bf883..3b3ba02f4 100644 --- a/hexa/workspaces/models.py +++ b/hexa/workspaces/models.py @@ -254,22 +254,21 @@ class Meta: on_delete=models.CASCADE, ) role = models.CharField(choices=WorkspaceMembershipRole.choices, max_length=50) - notebooks_server_hash = models.TextField(unique=True, blank=True) - access_token = models.TextField(max_length=50, null=True, blank=True, unique=True) + notebooks_server_hash = models.TextField(unique=True) + access_token = models.TextField(max_length=50, unique=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) objects = WorkspaceMembershipManager.from_queryset(WorkspaceMembershipQuerySet)() - def generate_access_token(self): - self.access_token = uuid.uuid4() - self.save() - def save(self, *args, **kwargs): if self.notebooks_server_hash == "": self.notebooks_server_hash = hashlib.blake2s( f"{self.workspace_id}_{self.user_id}".encode("utf-8"), digest_size=16 ).hexdigest() + if self.access_token == "": + self.access_token = uuid.uuid4() + super().save(*args, **kwargs) def update_if_has_perm(self, *, principal: User, role: WorkspaceMembershipRole): From 2bfeef5183bda9d3dcd838253b0f029f0927fec2 Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Thu, 21 Sep 2023 18:40:28 +0200 Subject: [PATCH 2/3] Fix(Workspaces): ensure the presence of a token (and a server hash) for each membership --- hexa/workspaces/tests/test_views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hexa/workspaces/tests/test_views.py b/hexa/workspaces/tests/test_views.py index 71d6f8a20..050942c5e 100644 --- a/hexa/workspaces/tests/test_views.py +++ b/hexa/workspaces/tests/test_views.py @@ -46,7 +46,11 @@ def setUpTestData(cls): countries=[{"code": "AL"}], ) - cls.WORKSPACE_MEMBERSHIP = WorkspaceMembership.objects.create( + cls.WORKSPACE_MEMBERSHIP_JULIA = WorkspaceMembership.objects.get( + workspace=cls.WORKSPACE, user=cls.USER_JULIA + ) + + cls.WORKSPACE_MEMBERSHIP_REBECCA = WorkspaceMembership.objects.create( user=cls.USER_REBECCA, workspace=cls.WORKSPACE, role=WorkspaceMembershipRole.VIEWER, @@ -125,6 +129,8 @@ def test_workspace_credentials_200( db_credentials = get_db_server_credentials() + self.maxDiff = None + response_data = response.json() self.assertEqual(response.status_code, 200) self.assertEqual( @@ -145,6 +151,9 @@ def test_workspace_credentials_200( "WORKSPACE_DATABASE_PASSWORD": self.WORKSPACE.db_password, "WORKSPACE_DATABASE_URL": self.WORKSPACE.db_url, "GCS_TOKEN": "gcs-token", + "HEXA_TOKEN": Signer().sign_object( + self.WORKSPACE_MEMBERSHIP_JULIA.access_token + ), }, ) self.assertEqual( From e303cb9a0c7f9e6ffc8ef0ded6456992da17e55c Mon Sep 17 00:00:00 2001 From: pvanliefland Date: Mon, 25 Sep 2023 10:04:40 +0200 Subject: [PATCH 3/3] Fix(Workspaces): ensure the presence of a token (and a server hash) for each membership --- hexa/workspaces/admin.py | 1 + ..._workspacemembership_access_token_and_more.py | 16 +++++++++++++--- hexa/workspaces/models.py | 14 ++++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/hexa/workspaces/admin.py b/hexa/workspaces/admin.py index b4ff35644..fc482e85f 100644 --- a/hexa/workspaces/admin.py +++ b/hexa/workspaces/admin.py @@ -28,6 +28,7 @@ class WorkspaceMembershipAdmin(admin.ModelAdmin): "created_at", "updated_at", ) + readonly_fields = ("notebooks_server_hash",) class ConnectionFieldInline(admin.StackedInline): diff --git a/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py b/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py index f4602ff3f..e753aaa71 100644 --- a/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py +++ b/hexa/workspaces/migrations/0037_alter_workspacemembership_access_token_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.7 on 2023-09-21 15:44 +# Generated by Django 4.1.7 on 2023-09-25 07:54 from django.db import migrations, models @@ -26,11 +26,21 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="workspacemembership", name="access_token", - field=models.TextField(max_length=50, unique=True), + field=models.TextField( + blank=True, + help_text="Access token that can be used to interact with the OpenHexa API (will be generated automatically, set to empty to regenerate a new token)", + max_length=50, + unique=True, + ), ), migrations.AlterField( model_name="workspacemembership", name="notebooks_server_hash", - field=models.TextField(unique=True), + field=models.TextField( + blank=True, + editable=False, + help_text="Can be used to identify a notebook server for a given user and workspace (will be generated automatically)", + unique=True, + ), ), ] diff --git a/hexa/workspaces/models.py b/hexa/workspaces/models.py index 3b3ba02f4..b91d7a37f 100644 --- a/hexa/workspaces/models.py +++ b/hexa/workspaces/models.py @@ -254,8 +254,18 @@ class Meta: on_delete=models.CASCADE, ) role = models.CharField(choices=WorkspaceMembershipRole.choices, max_length=50) - notebooks_server_hash = models.TextField(unique=True) - access_token = models.TextField(max_length=50, unique=True) + notebooks_server_hash = models.TextField( + blank=True, + unique=True, + help_text="Can be used to identify a notebook server for a given user and workspace (will be generated automatically)", + editable=False, + ) + access_token = models.TextField( + max_length=50, + blank=True, + unique=True, + help_text="Access token that can be used to interact with the OpenHexa API (will be generated automatically, set to empty to regenerate a new token)", + ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) objects = WorkspaceMembershipManager.from_queryset(WorkspaceMembershipQuerySet)()