From c602dea0e7d58798c29ff1d42baad0f1d9961c74 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Fri, 27 Oct 2023 11:23:24 +0300 Subject: [PATCH 1/6] fixed viewing of invited users --- cvat/apps/iam/rules/memberships.rego | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cvat/apps/iam/rules/memberships.rego b/cvat/apps/iam/rules/memberships.rego index ce0ba9225fb1..e424b04022c4 100644 --- a/cvat/apps/iam/rules/memberships.rego +++ b/cvat/apps/iam/rules/memberships.rego @@ -55,7 +55,7 @@ filter := [] { # Django Q object to filter list of entries qobject := [ {"organization": org_id} ] } else := qobject { org_id := input.auth.organization.id - qobject := [ {"organization": org_id}, {"is_active": true}, "&" ] + qobject := [ {"organization": org_id} ] } allow { @@ -76,7 +76,6 @@ allow { # himself/another maintainer/owner allow { { utils.CHANGE_ROLE, utils.DELETE }[input.scope] - input.resource.is_active input.resource.organization.id == input.auth.organization.id utils.has_perm(utils.USER) organizations.is_maintainer @@ -91,7 +90,6 @@ allow { # owner of the organization can change the role of any member and remove any member except himself allow { { utils.CHANGE_ROLE, utils.DELETE }[input.scope] - input.resource.is_active input.resource.organization.id == input.auth.organization.id utils.has_perm(utils.USER) organizations.is_owner From 01707ea9b0e7134a6b156975f7142ecb3ae01548 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Fri, 27 Oct 2023 12:20:41 +0300 Subject: [PATCH 2/6] fixed email auto verification --- cvat/apps/organizations/serializers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cvat/apps/organizations/serializers.py b/cvat/apps/organizations/serializers.py index 25b37e0a84cb..874e481c46ac 100644 --- a/cvat/apps/organizations/serializers.py +++ b/cvat/apps/organizations/serializers.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: MIT from django.contrib.auth import get_user_model +from allauth.account.models import EmailAddress +from allauth.account.adapter import get_adapter from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.contrib.auth.models import User @@ -91,7 +93,9 @@ def create(self, validated_data): user = User.objects.create_user(username=username, password=get_random_string(length=32), email=user_email, is_active=False) user.set_unusable_password() + email = EmailAddress.objects.create(user=user, email=user_email, primary=True, verified=False) user.save() + email.save() del membership_data['user'] membership, created = Membership.objects.get_or_create( defaults=membership_data, @@ -151,6 +155,8 @@ def save(self, request, invitation): self.cleaned_data = self.get_cleaned_data() user = invitation.membership.user user.is_active = True + email = EmailAddress.objects.get(email=user.email) + get_adapter(request).confirm_email(request, email) user.first_name = self.cleaned_data['first_name'] user.last_name = self.cleaned_data['last_name'] user.username = self.cleaned_data['username'] From 7bdf982b7c5027d61168ad02f0b23cb42157c456 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Fri, 27 Oct 2023 12:56:36 +0300 Subject: [PATCH 3/6] fixed invitations list --- cvat-ui/src/components/organization-page/member-item.tsx | 2 +- cvat/apps/iam/rules/memberships.rego | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cvat-ui/src/components/organization-page/member-item.tsx b/cvat-ui/src/components/organization-page/member-item.tsx index a52d9661233f..506bc75ad0f8 100644 --- a/cvat-ui/src/components/organization-page/member-item.tsx +++ b/cvat-ui/src/components/organization-page/member-item.tsx @@ -41,7 +41,7 @@ function MemberItem(props: Props): JSX.Element { const { username, firstName, lastName } = user; const { username: selfUserName } = useSelector((state: CombinedState) => state.auth.user); - const invitationActionsMenu = ( + const invitationActionsMenu = invitation && ( { if (action.key === MenuKeys.RESEND_INVITATION) { diff --git a/cvat/apps/iam/rules/memberships.rego b/cvat/apps/iam/rules/memberships.rego index e424b04022c4..9b2326055b45 100644 --- a/cvat/apps/iam/rules/memberships.rego +++ b/cvat/apps/iam/rules/memberships.rego @@ -54,8 +54,12 @@ filter := [] { # Django Q object to filter list of entries org_id := input.auth.organization.id qobject := [ {"organization": org_id} ] } else := qobject { + organizations.is_staff org_id := input.auth.organization.id qobject := [ {"organization": org_id} ] +} else := qobject { + org_id := input.auth.organization.id + qobject := [ {"organization": org_id}, {"is_active": true}, "&" ] } allow { From 9f1bbf2700b6765a69c29675a5fbe4a6c850193f Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Wed, 1 Nov 2023 14:46:15 +0300 Subject: [PATCH 4/6] fixed tests --- cvat/apps/iam/rules/memberships.rego | 6 ++++++ .../tests/generators/memberships_test.gen.rego.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/cvat/apps/iam/rules/memberships.rego b/cvat/apps/iam/rules/memberships.rego index 9b2326055b45..497b6fe58ebe 100644 --- a/cvat/apps/iam/rules/memberships.rego +++ b/cvat/apps/iam/rules/memberships.rego @@ -69,6 +69,12 @@ allow { input.resource.user.id == input.auth.user.id } +allow { + input.scope == utils.VIEW + organizations.is_staff + input.resource.organization.id == input.auth.organization.id +} + allow { input.scope == utils.VIEW input.resource.is_active diff --git a/cvat/apps/iam/rules/tests/generators/memberships_test.gen.rego.py b/cvat/apps/iam/rules/tests/generators/memberships_test.gen.rego.py index 6510084dcd6c..7cf9cfca255e 100644 --- a/cvat/apps/iam/rules/tests/generators/memberships_test.gen.rego.py +++ b/cvat/apps/iam/rules/tests/generators/memberships_test.gen.rego.py @@ -98,6 +98,16 @@ def eval_rule(scope, context, ownership, privilege, membership, data): return False if scope != "create" and not data["resource"]["is_active"]: + is_staff = membership == "owner" or membership == 'maintainer' + if is_staff: + if scope != 'view': + if ORG_ROLES.index(membership) >= ORG_ROLES.index(resource["role"]): + return False + if GROUPS.index(privilege) > GROUPS.index("user"): + return False + if resource["user"]['id'] == data["auth"]["user"]['id']: + return False + return True return False return bool(rules) From 907722c0caf5f8da8c0ca7ef5c1233ed1ff8be81 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Wed, 1 Nov 2023 14:55:16 +0300 Subject: [PATCH 5/6] fixed setting username --- cvat/apps/organizations/serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cvat/apps/organizations/serializers.py b/cvat/apps/organizations/serializers.py index 874e481c46ac..4e88d8423d77 100644 --- a/cvat/apps/organizations/serializers.py +++ b/cvat/apps/organizations/serializers.py @@ -89,8 +89,7 @@ def create(self, validated_data): del membership_data['user'] except ObjectDoesNotExist: user_email = membership_data['user']['email'] - username = user_email.split("@")[0] - user = User.objects.create_user(username=username, password=get_random_string(length=32), + user = User.objects.create_user(username=user_email, password=get_random_string(length=32), email=user_email, is_active=False) user.set_unusable_password() email = EmailAddress.objects.create(user=user, email=user_email, primary=True, verified=False) From 5f0f517b1161575a8a0998abaca0ae44cbb67481 Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Thu, 2 Nov 2023 16:41:20 +0300 Subject: [PATCH 6/6] added @transaction.atomic to `create` --- cvat/apps/organizations/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cvat/apps/organizations/serializers.py b/cvat/apps/organizations/serializers.py index 4e88d8423d77..b88d6e2f3d12 100644 --- a/cvat/apps/organizations/serializers.py +++ b/cvat/apps/organizations/serializers.py @@ -10,6 +10,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.utils.crypto import get_random_string +from django.db import transaction from rest_framework import serializers from distutils.util import strtobool @@ -80,6 +81,7 @@ class Meta: fields = ['key', 'created_date', 'owner', 'role', 'organization', 'email'] read_only_fields = ['key', 'created_date', 'owner', 'organization'] + @transaction.atomic def create(self, validated_data): membership_data = validated_data.pop('membership') organization = validated_data.pop('organization')