Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement warnings #688

Closed
wants to merge 10 commits into from
Closed

Implement warnings #688

wants to merge 10 commits into from

Conversation

hectorgomezv
Copy link
Member

Relates to #675

This PR aims to create a new warning field for each Chain. This new warning field is nullable and should be updatable by superusers, and also for support collaborators. That's why a new support group is created. The idea is to make all Chain fields read-only for support collaborators, except warning, so this group can only edit the warning field.

Using this approach we avoid the usage of third party libraries or external dependencies. It's implemented by just using Django features. Feedback is welcomed! 🙂

@hectorgomezv hectorgomezv requested a review from a team as a code owner October 25, 2022 08:50
@coveralls
Copy link

coveralls commented Oct 25, 2022

Pull Request Test Coverage Report for Build 3446239714

  • 23 of 28 (82.14%) changed or added relevant lines in 4 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.5%) to 99.227%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/chains/admin.py 3 8 37.5%
Totals Coverage Status
Change from base Build 3417671914: -0.5%
Covered Lines: 899
Relevant Lines: 906

💛 - Coveralls

def get_readonly_fields(
self, request: HttpRequest, obj: Optional[Chain] = None
) -> List[str]:
if request.user.groups.filter(name="support"): # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about having a new table for the warning messages? ChainId + Message.

The endpoint can return the message from this table. The permission then can be done on the table level. Maybe it's easier and simpler this way wdyt?

Copy link
Member

@Uxio0 Uxio0 Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer not if it's possible to do it in another way, I like having a normalized database if possible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more about this field and in a way it seems that it's not really part of the chain metadata so that's why I thought about a separate table. Even though it's possible in a different way the Chain model itself already has quite some fields. But just an idea (nothing that would block the feature).

Copy link
Member Author

@hectorgomezv hectorgomezv Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking in having a separated table as well, it could be nice to keep an history of the changes as well, but I initially discarded it because it seems a bit overkill for the feature.

About the custom permission... it was my initial approach as well, but in this case we need to still grant edit permission over all Chain fields to superusers, and only over warning field for a selected group of users. I thinking in creating an additional permission, something like can_change_all_chain_fields and when a user doesn't have it, show all the fields as read-only except warning. Do you see a simpler way here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm missing something, but why not using if request.user.has_perm("app.permission_name")?

Copy link
Member Author

@hectorgomezv hectorgomezv Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's me the one missing something, but we need to avoid support group from modifying any field except for warning right? So it's kind of negative permission. I mean, all the other fields are the ones to restrict to some users (support), right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it more like:

  • By default users don't have any permission unless you make them superusers or give them permissions to every table individually.
  • support group will grant the edit chains and change_only_warning to all his members.
  • To edit the chains table users will need the edit chains permission
  • People with change_only_warning permission will be able to modify only warning field, so if request.user.has_perm(change_only_warning): every field but warning will be read_only

Copy link
Member Author

@hectorgomezv hectorgomezv Nov 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Uxio0 thanks for your feedback and guidance on this 🙂. I was doing some tests and it seems that when creating a new permission (change_only_warning in this case), that permission is automatically inherited by superuser(s). So, in this case, all fields are converted to read_only for the superusers as well (except warning of course). 🤔 I see two options here:

  • Rely on the group.
  • Have an additional check to verify the user is not a superadmin before marking the fields as read-only.
if (
    request.user.has_perm("change_only_warning")
    and not request.user.is_superuser
):

I think the second option is better, what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented the second option, of course if you see any concerns with it let me know 🙂

@@ -119,6 +119,7 @@ class RpcAuthentication(models.TextChoices):
recommended_master_copy_version = models.CharField(
max_length=255, validators=[sem_ver_validator]
)
warning = models.CharField(max_length=255, blank=True, null=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a TextField?

Copy link
Member Author

@hectorgomezv hectorgomezv Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks! This is something I forgot to annotate in the PR, but I was doubting about it as well 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 02e17b2


def create_support_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
support_group, created = Group.objects.get_or_create(name="support")
permission = Permission.objects.filter(codename="change_chain")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this permission defined? It should be on Meta for the Chain model: https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#custom-permissions

support_group, created = Group.objects.get_or_create(name="support")
permission = Permission.objects.filter(codename="change_chain")
if permission:
support_group.permissions.add(permission[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you only expect to get one permission you can do Permission.objects.get(codename="change_chain")

def create_support_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
support_group, created = Group.objects.get_or_create(name="support")
permission = Permission.objects.filter(codename="change_chain")
if permission:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permission must exist we shouldn't check this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting an error when running tests due to this, that's why I included the condition. Maybe it's related with not having the permission defined in Meta as you said in the previous comment...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's it. Permission should be created in a migration when adding it to Meta, and you can use that migration to add it to the group. Also, if possible, do everything in the same migration

Copy link
Member Author

@hectorgomezv hectorgomezv Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Uxio0 I was testing a lot trying to understand why the migration was failing (because the permission wasn't found), I think the reason why we need Permission.objects.get_or_create here instead of simply Permission.objects.get is because the permissions data is not inferred/written to the database until all migrations are executed (using the post_migrate signal if I'm not wrong).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this issue is related. This also explains why test were failing in my local setup (I understand the whole list of migrations gets executed against a test database when running the tests).

@hectorgomezv hectorgomezv requested a review from Uxio0 November 3, 2022 16:50
@luarx luarx removed their request for review November 3, 2022 21:54
src/chains/migrations/0037_chain_warning.py Show resolved Hide resolved
@@ -129,6 +130,9 @@ def get_disabled_wallets(self) -> QuerySet["Wallet"]:
def __str__(self) -> str:
return f"{self.name} | chain_id={self.id}"

class Meta:
permissions = [("change_only_warning", "Can change only warning")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name it change_warning with the description: Can change the chain warning.

I think "only" might be redundant if the permission is tied to this field.

Copy link
Member Author

@hectorgomezv hectorgomezv Nov 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only is included in the name because of the nature of this permission. This permission will imply two things for an user:

  • The user can edit the warning field.
  • The user cannot edit any other Chain field, i.e. the rest of the fields will be marked as read-only.

I know it is not very intuitive, but we agreed on taking this approach to avoid using a third party dependency which would let us define field-level permissions. Even using that dependency, I suspect the outcome wouldn't be very intuitive either, because our goal is not to grant new permissions, but preventing some users to access some fields.

@@ -118,6 +118,7 @@ class ChainSerializer(serializers.ModelSerializer[Chain]):
ens_registry_address = EthereumAddressField()
disabled_wallets = serializers.SerializerMethodField()
features = serializers.SerializerMethodField()
warning = serializers.CharField()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there any decision on the name of this field? We should be aligned with the clients regarding this.

Can this field be used to have just some information out (which is not a warning) for example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is named warning because of the issue this change is related: New "warning" text field in chain configs 🙂 But for sure we can sync about of this.

) -> List[str]:
if (
request.user.has_perm("chains.change_only_warning")
and not request.user.is_superuser
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we allow a superuser to change this field? (even without the permission)

Copy link
Member Author

@hectorgomezv hectorgomezv Nov 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Please refer to the previous comment for reference)

This function (get_readonly_fields) returns the read-only fields, so for an user which have the change_only_warning and is not a superuser, will return all the Chain fields except warning. For superusers, it will return [], so all the fields are editable. In short, we allow superusers to change this field.

(I know this code is not super intuitive to read, please let me know if you find a better approach).

@@ -195,6 +196,7 @@ def test_json_payload_format(self) -> None:
"recommendedMasterCopyVersion": chain.recommended_master_copy_version,
"disabledWallets": [],
"features": [],
"warning": None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are missing a test if the warning is set.

The factory method should also be updated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 02e17b2 👍🏻

@hectorgomezv
Copy link
Member Author

I'm drafting this PR since there is an ongoing discussion in Notion.

@hectorgomezv hectorgomezv marked this pull request as draft November 11, 2022 15:54
@hectorgomezv
Copy link
Member Author

I'm closing this PR because the source issue is also closed now. Please reach out if you want to discuss this feature further 🙂

@github-actions github-actions bot locked and limited conversation to collaborators Apr 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants